> ## Documentation Index
> Fetch the complete documentation index at: https://docs.plastics.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Website Search API and UI walkthrough

> Connect a server-side adapter and accessible JavaScript search results to your website.

The implementation is in [integrations/website-search on GitHub](https://github.com/plastik-ai/codebase-render-trial/tree/main/integrations/website-search). It includes an API adapter, a browser module and a runnable Node example. These are source files to integrate into your application, not a published npm package. Repository access may be required.

## 1. Understand the API

Your **backend** sends `POST /v1/search` to the gateway address provided by Plastics. Add `Authorization: Bearer <connection credential>` and `Content-Type: application/json`. Keep the credential in server secret configuration and redact it from logs.

```json theme={null}
{
  "query": "scratch resistance",
  "page": 1,
  "page_size": 10,
  "filters": { "brand": [], "category": [] },
  "language": "en",
  "region": "US"
}
```

Only `query` is required. Page defaults to 1 and page size to 10; page is limited to 100 and page size to 20. Queries allow 1–500 characters. Brand/category filters each accept up to 10 values of up to 100 characters. Omit language/region to use the connection defaults, or send matching values. Unknown fields are rejected; visitors cannot choose another organization or publication scope.

An illustrative response is:

```json theme={null}
{
  "version": "1",
  "results": [
    {
      "id": "11111111-1111-4111-8111-111111111111",
      "title": "Approved product name",
      "description": "The public description approved for this connection.",
      "url": "https://www.example.com/products/approved-product"
    }
  ],
  "total": 1,
  "page": 1,
  "page_size": 10
}
```

Result fields are limited to ID, title, description and URL. There are no private document snippets, generated answers or model diagnostics. Requests are limited to 8 KiB and responses to 512 KiB of UTF-8 JSON. `GET /healthz` is a basic health check, not proof that credentials or search work.

Run this from your backend environment with `SEARCH_GATEWAY_ORIGIN` and
`SEARCH_PARTNER_CREDENTIAL` already set securely. The header is passed over stdin
rather than placing the secret value in command history or the process arguments.
Do not enable shell tracing or curl verbose logging.

```shell theme={null}
printf 'header = "Authorization: Bearer %s"\n' "$SEARCH_PARTNER_CREDENTIAL" | \
  curl --config - --fail-with-body --silent --show-error \
  --max-time 12 \
  --header 'Content-Type: application/json' \
  --data '{"query":"scratch resistance","page":1,"page_size":10}' \
  "${SEARCH_GATEWAY_ORIGIN%/}/v1/search"
```

A `200` with approved results verifies this authenticated API path. Also check
an expected published product and invalid/revoked credentials; a health check alone
is insufficient. Use an enabled staging connection, not an unrelated live connection.

## 2. Add the backend adapter

Copy the repository's `server.mjs`, `contract.mjs` and `bounded-json.mjs` into your server project. Create a handler using values from your server configuration:

```javascript theme={null}
import { createSearchHandler } from "./server.mjs";

const search = createSearchHandler({
  gatewayOrigin: process.env.SEARCH_GATEWAY_ORIGIN,
  credential: process.env.SEARCH_PARTNER_CREDENTIAL,
  websiteOrigin: "https://www.example.com",
  productOrigins: ["https://www.example.com"],
  consumeRateLimit: async (trustedClientId) => {
    // Implement this using your shared server-side rate limiter.
    return rateLimiter.consume(trustedClientId);
  },
});

// In your framework's POST /api/website-search route:
// return await search(webStandardRequest, trustedClientId);
```

`rateLimiter` and the route wiring above are application-specific. Supply a server-derived client identifier; do not trust a visitor-supplied identity header. The limiter must return whether the request is allowed. The runnable example shows Node request/response wiring and an in-memory limiter for local development; use a shared limiter for multiple production instances.

The adapter fixes the upstream route, rejects cross-origin requests and unsupported fields, limits response size and gives visitors generic errors. Origin checks are not visitor authentication. Add your own authentication if your website requires it. Do not turn this route into an arbitrary URL proxy, or accept a credential or gateway address from the browser.

## 3. Bind your search bar

Serve `client.mjs`, `contract.mjs` and `bounded-json.mjs` from the same origin as your page. Adapt your existing markup, or start with:

```html theme={null}
<form id="product-search" action="/your-existing-search" method="get">
  <label for="query">Search products</label>
  <input id="query" name="q" type="search" required maxlength="500" />
  <button type="submit">Search</button>
</form>
<section id="results" aria-label="Search results"></section>
<script type="module" src="/search-setup.mjs"></script>
```

In `search-setup.mjs`:

```javascript theme={null}
import { bindWebsiteSearch } from "/client.mjs";

const search = bindWebsiteSearch({
  form: "#product-search",
  input: "#query",
  results: "#results",
  endpoint: "/api/website-search",
  productOrigins: ["https://www.example.com"],
});

// Optional exact filters; changing filters resets pagination.
// search.setFilters({ brand: ['Approved brand'], category: [] });
// Call search.destroy() when removing this page in a client-side app.
```

Replace the form action with your working original search route. The client submits on Enter or the search button, announces loading/results through a polite live region, marks the search form busy, cancels stale requests and adds paging controls. Empty results and errors are explicit; errors offer **Use original search**. Product titles/descriptions are rendered as text, and links are checked against approved HTTPS origins. If supplying your own renderer with `onResults`, retain those safety and accessibility properties and never inject result text as HTML.

The default time budgets are 15 seconds in the browser and 12 seconds in the adapter, with a shorter gateway budget. Avoid shortening the outer budget below the inner request time without providing an intentional fallback experience.

## 4. Run the example and verify

Use Node 22 or later. From `integrations/website-search`, set the server-only environment variables `SEARCH_GATEWAY_ORIGIN`, `SEARCH_PARTNER_CREDENTIAL` and `SEARCH_PRODUCT_ORIGIN`, then run:

```shell theme={null}
node example/server.mjs
```

Open `http://localhost:8080`. This local example uses a real configured gateway and credential. The product origin must match the approved HTTPS product links. It does not install anything on your company's website. Replace the example's placeholder original-search route before using it on staging.

Run the repository checks with:

```shell theme={null}
node --test test/*.test.mjs
```

Complete the [staging verification and credential lifecycle checks](https://docs.plastics.com/integrations/website-search/configuration.md#what-the-test-verifies) before switching live traffic. Keep credentials out of screenshots, browser tools, terminal history and committed configuration.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.