# Agent Onboarding Guide (/docs/agents/agent-onboarding)

This page is written for **coding agents** (Claude Code, Cursor, and similar tools) that have been asked to set up TheWebRatings on a website. It is exact and complete: follow the steps in order, handle every error code in the table, and respect the placement rules.

TheWebRatings (TWR) collects verified in-app reviews for SaaS products via an embeddable widget. Each website gets a public profile page at `https://thewebratings.com/apps/{slug}`. The Pro plan unlocks display widgets (badge, bar, carousel), review replies, and disputes.

A machine-readable markdown version of this page is available at [https://docs.thewebratings.com/agent-onboarding.md](https://docs.thewebratings.com/agent-onboarding.md). Prefer the [MCP server](/docs/agents/mcp-server) if your environment supports MCP — it wraps this exact flow in 7 tools.

***

## What you need before starting

One thing only: a **setup token** from the site owner.

* Format: `twr_agent_…`
* Minted by the owner in **Dashboard → Settings** at thewebratings.com
* Expires **7 days** after creation
* Becomes **bound to ONE domain** on first use — you cannot reuse it for a second site

If you do not have a token, stop and ask the owner to mint one. Do not guess or fabricate tokens.

You also need to know the **domain** being onboarded (e.g. `acmecrm.com`) and have write access to the site's own codebase plus the ability to deploy it (or a human who will deploy for you).

## API conventions

* **Base URL:** `https://api.thewebratings.com/api/v1`
* **Auth header (every request):** `X-Setup-Token: twr_agent_…`
* **Response envelope (every response):**

```json
{
  "success": true,
  "status_code": 200,
  "message": "OK",
  "data": { },
  "error": null,
  "meta": {}
}
```

On failure, `success` is `false` and `error` is `{"code": "SOME_CODE", "details": {…}}`. Branch on `error.code`, not on the message text.

## The golden rule

<Callout type="info" title="Always call setup/status first">
  `GET /partners/setup/status/` is the **stateless source of truth**. Call it:

  1. Before doing anything else
  2. After any restart, crash, or context loss
  3. Whenever you are unsure what state the setup is in

  Its `next_steps` array tells you exactly what to do next. Never assume state — always re-check.
</Callout>

***

## Step 1: Check setup status

```http
GET /partners/setup/status/?domain=acmecrm.com
X-Setup-Token: twr_agent_8fK2mQ9xL4vN7pR3sT6wY1zB5cD0eH8j
```

Response (fresh site, nothing done yet):

```json
{
  "success": true,
  "status_code": 200,
  "message": "Setup status retrieved",
  "data": {
    "domain": "acmecrm.com",
    "exists": false,
    "website": null,
    "claimed_by_me": false,
    "claimed_by_other": false,
    "verified": false,
    "verification_pending": false,
    "has_api_key": false,
    "api_key": null,
    "plan": "free",
    "display_widgets_allowed": false,
    "next_steps": ["create_website"]
  },
  "error": null,
  "meta": {}
}
```

Response (mid-flow example — website created, verification started, tag not yet verified):

```json
{
  "success": true,
  "status_code": 200,
  "message": "Setup status retrieved",
  "data": {
    "domain": "acmecrm.com",
    "exists": true,
    "website": { "id": 4821, "slug": "acme-crm", "name": "Acme CRM" },
    "claimed_by_me": true,
    "claimed_by_other": false,
    "verified": false,
    "verification_pending": true,
    "has_api_key": false,
    "api_key": null,
    "plan": "free",
    "display_widgets_allowed": false,
    "next_steps": ["add_meta_tag", "verify_claim"]
  },
  "error": null,
  "meta": {}
}
```

### `next_steps` vocabulary

Act on these values, in the order returned:

| Value                         | What to do                                                 |
| ----------------------------- | ---------------------------------------------------------- |
| `create_website`              | Step 2                                                     |
| `start_claim_verification`    | Step 3                                                     |
| `add_meta_tag`                | Step 4                                                     |
| `verify_claim`                | Step 5                                                     |
| `install_collection_widget`   | Steps 6–7                                                  |
| `install_display_widgets`     | Step 8 (Pro is active)                                     |
| `upgrade_for_display_widgets` | Skip display widgets; relay upgrade link to owner (Step 8) |
| `done`                        | Nothing left; run Step 9 validation and report             |

If `claimed_by_other` is `true`, **stop immediately** — the domain is verified by another account. Tell the owner to contact [support@thewebratings.com](mailto:support@thewebratings.com).

## Step 2: Create the website

```http
POST /partners/websites/
X-Setup-Token: twr_agent_8fK2mQ9xL4vN7pR3sT6wY1zB5cD0eH8j
Content-Type: application/json

{
  "domain": "acmecrm.com",
  "name": "Acme CRM",
  "app_url": "https://app.acmecrm.com",
  "website_url": "https://acmecrm.com",
  "category": "crm"
}
```

Only `domain` is required. Response — `201` when created, `200` when the domain is already linked to this account (both are success; proceed either way):

```json
{
  "success": true,
  "status_code": 201,
  "message": "Website created",
  "data": {
    "id": 4821,
    "slug": "acme-crm",
    "name": "Acme CRM",
    "domain": "acmecrm.com",
    "app_url": "https://app.acmecrm.com",
    "verified": false
  },
  "error": null,
  "meta": {}
}
```

## Step 3: Start claim verification

```http
POST /partners/websites/start-claim-verification/
X-Setup-Token: twr_agent_8fK2mQ9xL4vN7pR3sT6wY1zB5cD0eH8j
Content-Type: application/json

{
  "domain": "acmecrm.com",
  "method": "html"
}
```

Response:

```json
{
  "success": true,
  "status_code": 200,
  "message": "Verification started",
  "data": {
    "verification_token": "twr-verify-9f8e7d6c5b4a3a2b1c0d",
    "html_meta_tag": "<meta name=\"twr-site-verification\" content=\"twr-verify-9f8e7d6c5b4a3a2b1c0d\">",
    "website_id": 4821,
    "status": "pending"
  },
  "error": null,
  "meta": {}
}
```

Save `html_meta_tag` — you will place it in the customer's codebase next.

## Step 4: Add the meta tag to the customer's codebase and deploy

This step happens **in the site's own repository**, not via the API. The meta tag must end up in the `<head>` of the page served at the site's app URL, live on the internet, before Step 5 can succeed.

### Next.js (App Router)

Preferred — use the Metadata API in `app/layout.tsx`:

```tsx
export const metadata = {
  other: {
    'twr-site-verification': 'twr-verify-9f8e7d6c5b4a3a2b1c0d',
  },
};
```

Or place the raw tag directly inside `<head>` in the root layout.

### Plain HTML / server-rendered templates

Paste the tag verbatim into the `<head>` of the main HTML file or base template:

```html
<head>
  <meta name="twr-site-verification" content="twr-verify-9f8e7d6c5b4a3a2b1c0d">
  ...
</head>
```

### SPAs (Vite, CRA, Vue CLI, Angular)

Add the tag to the static `index.html` in the project root or `public/` directory — **not** via JavaScript at runtime. The verifier fetches raw HTML; tags injected by client-side JS after page load may not be seen. If the SPA is served behind SSR/prerendering, ensure the tag is present in the initial HTML response.

**Then deploy.** Confirm the tag is actually live (e.g. `curl -s https://app.acmecrm.com | grep twr-site-verification`) before calling verify. If deployment is manual, hand off to the owner and resume from Step 1 (status check) once deployed.

## Step 5: Verify the claim

```http
POST /partners/websites/verify-claim/
X-Setup-Token: twr_agent_8fK2mQ9xL4vN7pR3sT6wY1zB5cD0eH8j
Content-Type: application/json

{
  "domain": "acmecrm.com"
}
```

Response:

```json
{
  "success": true,
  "status_code": 200,
  "message": "Domain verified",
  "data": {
    "status": "verified",
    "domain": "acmecrm.com",
    "website_id": 4821
  },
  "error": null,
  "meta": {}
}
```

Successful verification **auto-creates a default API key** for the website. You do not need a separate key-creation call — fetch it via Step 1 (`data.api_key`) or Step 6 (embedded in `collection_snippet`).

If you get `VERIFICATION_FAILED`, read `error.details.failure_reason`, confirm the tag is deployed at the correct URL, wait for the deploy/CDN to settle, and retry. Do not retry in a tight loop — wait at least 30–60 seconds between attempts.

## Step 6: Fetch the snippets

```http
GET /partners/setup/snippets/?domain=acmecrm.com
X-Setup-Token: twr_agent_8fK2mQ9xL4vN7pR3sT6wY1zB5cD0eH8j
```

Response:

```json
{
  "success": true,
  "status_code": 200,
  "message": "Snippets retrieved",
  "data": {
    "verification_meta_tag": null,
    "collection_snippet": "<script src=\"https://cdn.thewebratings.com/twr.min.js\"></script>\n<script>TheWebRatings.init({ apiKey: 'twr_live_a1b2c3d4e5f60718293a4b5c' });</script>",
    "collection_note": "Install only inside authenticated areas of the app.",
    "display_script_tag": "<script src=\"https://cdn.thewebratings.com/twr-display.min.js\" defer></script>",
    "display_snippets": {
      "badge": "<div data-twr-widget=\"badge\" data-twr-slug=\"acme-crm\" data-twr-theme=\"auto\" data-twr-size=\"md\"></div>",
      "bar": "<div data-twr-widget=\"bar\" data-twr-slug=\"acme-crm\" data-twr-theme=\"auto\" data-twr-size=\"md\"></div>",
      "carousel": "<div data-twr-widget=\"carousel\" data-twr-slug=\"acme-crm\" data-twr-theme=\"auto\" data-twr-size=\"md\"></div>"
    },
    "display_requires_pro": true,
    "display_widgets_allowed": false,
    "upgrade_url": "https://thewebratings.com/pricing",
    "profile_url": "https://thewebratings.com/apps/acme-crm",
    "docs_url": "https://docs.thewebratings.com"
  },
  "error": null,
  "meta": {}
}
```

`verification_meta_tag` is `null` once the site is verified. `collection_snippet` is `null` until verification succeeds (no API key exists yet).

## Step 7: Install the collection widget

<Callout type="warn" title="Authenticated areas ONLY">
  The collection widget **auto-prompts users to leave a review**. Install it **only** inside logged-in / authenticated areas of the app (dashboard layout, member portal, admin panel). Installing it on public pages will prompt anonymous visitors, who cannot leave verified reviews. Never put it in a public root layout, landing page, or marketing page.
</Callout>

Add to the authenticated layout of the customer's app:

```html
<script src="https://cdn.thewebratings.com/twr.min.js"></script>
<script>TheWebRatings.init({ apiKey: 'twr_live_a1b2c3d4e5f60718293a4b5c' });</script>
```

Use the exact `apiKey` from Step 6's `collection_snippet` (or `api_key` from Step 1). In React/Next.js apps, load the script in the authenticated layout component and call `TheWebRatings.init` after it loads (e.g. `next/script` with `onLoad`, or a `useEffect` that appends the script). See [framework guides](/docs/frameworks) for full examples.

## Step 8: Install display widgets (Pro only)

Display widgets show collected reviews on **public marketing pages** (landing page, pricing page, footer). They are the opposite of the collection widget: public pages only.

Check `display_widgets_allowed` from Step 1 or Step 6. The server enforces this — it is not a client-side flag you can bypass.

**If `display_widgets_allowed` is `false`:** install **nothing** for display. Finish the remaining steps, then tell the owner verbatim intent: "Upgrade at [https://thewebratings.com/pricing](https://thewebratings.com/pricing) to enable display widgets, then re-run." Do not treat this as an error.

**If `true`:** add the script once per page, then one `<div>` per widget:

```html
<script src="https://cdn.thewebratings.com/twr-display.min.js" defer></script>
<div data-twr-widget="badge" data-twr-slug="acme-crm" data-twr-theme="auto" data-twr-size="md"></div>
```

| Attribute         | Values                                           |
| ----------------- | ------------------------------------------------ |
| `data-twr-widget` | `badge` \| `bar` \| `carousel`                   |
| `data-twr-slug`   | The website slug from Step 1/2 (e.g. `acme-crm`) |
| `data-twr-theme`  | `light` \| `dark` \| `auto`                      |
| `data-twr-size`   | `sm` \| `md` \| `lg`                             |

A sensible default: `badge` in the site footer or near pricing CTAs. Use the exact snippets from Step 6's `display_snippets`.

## Step 9: Validate the installation

After deploying the widget changes, ask the API to fetch a live page and confirm what it finds:

```http
POST /partners/setup/validate/
X-Setup-Token: twr_agent_8fK2mQ9xL4vN7pR3sT6wY1zB5cD0eH8j
Content-Type: application/json

{
  "url": "https://acmecrm.com/"
}
```

Response:

```json
{
  "success": true,
  "status_code": 200,
  "message": "Validation complete",
  "data": {
    "url_checked": "https://acmecrm.com/",
    "fetched_status": 200,
    "collection_widget_found": false,
    "display_widget_found": true,
    "display_widget_types_found": ["badge"],
    "meta_tag_found": true
  },
  "error": null,
  "meta": {}
}
```

Validate the pages you actually changed. Note: the collection widget lives behind login, so a public-URL validation will correctly report `collection_widget_found: false` — that is expected, not a failure. Validate a public marketing page for display widgets, and rely on code review + deploy logs for the authenticated collection snippet.

***

## Error codes

Handle every code below. `error.code` is the branching key.

| Code                      | HTTP | Meaning                                                               | Action                                                                                   |
| ------------------------- | ---- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `TOKEN_INVALID`           | 401  | Token malformed or unknown                                            | Ask the owner to mint a fresh token in Dashboard → Settings                              |
| `TOKEN_REVOKED`           | 401  | Token was revoked                                                     | Ask the owner to mint a fresh token in Dashboard → Settings                              |
| `TOKEN_EXPIRED`           | 401  | Token older than 7 days                                               | Ask the owner to mint a fresh token in Dashboard → Settings                              |
| `SETUP_TOKEN_NOT_ALLOWED` | 403  | Endpoint not permitted with a setup token                             | Do not retry; use only the endpoints documented on this page                             |
| `TOKEN_SCOPE`             | 403  | Token is bound to a different domain                                  | Ask the owner to mint a new token for this site                                          |
| `ALREADY_CLAIMED`         | 409  | Site verified by another account                                      | Stop; owner should contact [support@thewebratings.com](mailto:support@thewebratings.com) |
| `NO_ATTEMPT`              | 400  | verify-claim called before start-claim-verification                   | Call Step 3 first, then retry                                                            |
| `VERIFICATION_FAILED`     | 400  | Meta tag not found at the app URL (`details.failure_reason` explains) | Confirm the tag is deployed and the URL is correct, wait for the deploy, retry           |
| `RACE_CONDITION`          | 409  | Someone else verified this domain first                               | Stop and report to the owner                                                             |
| `PLAN_LIMIT_REACHED`      | 403  | Website limit reached (free = 1, pro = 5)                             | Tell the owner to upgrade at thewebratings.com/pricing                                   |
| `UPGRADE_REQUIRED`        | 403  | Pro feature on a free plan                                            | Finish everything else; relay the upgrade link to the owner                              |
| `WEBSITE_NOT_FOUND`       | 404  | Domain has no website record                                          | Re-check with the status endpoint (Step 1)                                               |
| `NOT_OWNER`               | 403  | Website not owned by this account                                     | Re-check with the status endpoint (Step 1)                                               |
| `URL_NOT_ALLOWED`         | 400  | Validate URL is blocked (wrong domain, non-https, private)            | Use a public https URL on the claimed domain                                             |
| `UNREACHABLE`             | 400  | Validate could not fetch the site                                     | Confirm the site is live and publicly reachable, then retry                              |

***

## Placement rules (summary)

| Widget                                 | Where                                                          | Why                                                                                 |
| -------------------------------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| Collection widget (`twr.min.js`)       | **Authenticated areas only** — dashboard, member portal, admin | It auto-prompts the current user for a review; anonymous visitors must never see it |
| Display widgets (`twr-display.min.js`) | **Public marketing pages** — landing, pricing, footer          | They show social proof to prospects; Pro plan required, server-enforced             |
| Verification meta tag                  | `<head>` of the page at the site's app URL, in raw HTML        | The verifier fetches raw HTML; runtime-injected tags may not be detected            |

## Final checklist

Before reporting completion to the owner, confirm:

* [ ] `GET /partners/setup/status/` returns `verified: true` and `has_api_key: true`
* [ ] Verification meta tag is live in the deployed site's `<head>` (leave it in place — do not remove it after verification)
* [ ] Collection snippet installed **only** in authenticated layouts, with the real API key (no placeholder)
* [ ] Display widgets: installed on public pages if `display_widgets_allowed: true`; otherwise nothing installed and the upgrade message relayed to the owner
* [ ] `POST /partners/setup/validate/` run against a changed public page; results reported
* [ ] `next_steps` from the status endpoint is `["done"]` (or `["upgrade_for_display_widgets"]` on the free plan)
* [ ] Reported to the owner: what was installed, where, the profile URL (`https://thewebratings.com/apps/{slug}`), and any remaining manual actions

## See also

* [MCP server](/docs/agents/mcp-server) — the same flow as 7 MCP tools for Claude Code and Cursor
* [Quick Install](/docs/getting-started/quick-install) — human-oriented widget install guide
* [Framework guides](/docs/frameworks) — React, Vue, Next.js integration details
* [https://docs.thewebratings.com/agent-onboarding.md](https://docs.thewebratings.com/agent-onboarding.md) — this page as plain markdown
* [https://docs.thewebratings.com/llms.txt](https://docs.thewebratings.com/llms.txt) — index of all docs for LLMs
