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

# Use the JavaScript API

> Look up every method, option, and callback of the guidelines library as it ships.

The library ships as `https://app.surferseo.com/static/surfer_guidelines_1_x_x.js`, which tracks the 1.x line ([Versioning](#versioning)), and puts three functions on `window.surferGuidelines`. The [quickstart](/integrations/embed/quickstart) shows the loader and one working call. Embedding Surfer needs the arrangement and the browser requirements under [Availability](/integrations/embed/quickstart#availability).

The panel the library mounts shows one Content Editor, the document Surfer builds around your target keywords, holding the guidelines and the Content Score; the panel scores the HTML you send against those guidelines ([The guidelines panel](/integrations/guidelines-panel)).

The library exchanges postMessage commands with the panel's iframe and is the supported client, so the message format is not documented.

## window\.surferGuidelines.init

`init` mounts the panel inside an element you own and returns nothing; use it when the four callbacks it accepts are enough.

The declaration, as the library ships it:

```ts theme={"system"}
init(
  target: HTMLElement,
  sharingToken?: string,
  options?: {
    permalink?: string
    anonymous?: boolean
    partner?: string
    onContentScoreChanged?: (contentScore: number) => void
    onContentScoreDetailsChanged?: (scoreDetails: ContentScoreDetails) => void
    onTermsToUseChanged?: (termsToUse: any[]) => void
    onFocusTermChanged?: (focusTerm: any) => void
  }
): void
```

Parameters:

| Parameter | Type | Required | Description |
| - | - | - | - |
| `target` | `HTMLElement` | Yes | The element the iframe is appended to. |
| `sharingToken` | `string` | No | A secret the library prefixes to the permalink so that a guessable page URL does not become a guessable permalink; none by default. [Access and sharing](/integrations/embed/access-and-sharing) covers when to use one. |
| `options` | object | No | `permalink`, `anonymous`, `partner`, and the four callbacks in the declaration; [Options](#options) describes each. `permalink` defaults to the page URL without its `#` fragment. |

What `init` does, in order:

1. Creates the iframe, adds the `surfer-guidelines` class to it, and appends it to `target`, unsized; see `$iframe` under [The handle](#the-handle).
2. Sets the permalink from `options.permalink`, the page URL, and `sharingToken`, as [Permalinks](/integrations/embed/access-and-sharing#permalinks) describes.
3. Sends the HTML from any `window.surferGuidelines.setHtml` call made before `init` ran.
4. Points `window.surferGuidelines.setHtml` at the mounted panel.

`onNavigation`, `onDraftLoaded`, and `onStructuralGuidelinesChanged` are not among `init`'s options, and `init` returns no handle. `initWithOptions` below covers both.

## window\.surferGuidelines.setHtml

`setHtml` sends the article HTML to the panel. Call it after every change to the text.

The declaration:

```ts theme={"system"}
setHtml(html: string): void
```

`html` is the article's HTML only; the panel does not extract an article from a whole page. The panel ignores HTML identical to what it already holds.

Calls made before `init` are kept, last one wins, and sent once `init` runs. After `init` this is the same function as the handle's `setHtml`.

## window\.surferGuidelines.initWithOptions

`initWithOptions` creates the iframe without attaching it and returns a handle with the iframe element and every method. Use it for the callbacks `init` does not take, or to place the iframe yourself.

The declaration below shows the library as it behaves at runtime. The shipped declaration file types `getMatchedTerms` as synchronous, lists a view the panel no longer honors, and lists two `configureView` flags the panel ignores:

```ts theme={"system"}
initWithOptions(opts?: FrameCommunicatorOptions): {
  $iframe: HTMLIFrameElement
  setPermalink: (permalink: string | null) => void
  setHtml: (html: string | null) => void
  requestView: (requestedView: 'guidelines' | 'content_score') => void
  refreshDraft: () => void
  configureView: (
    config: Readonly<{
      disableBatchContentEditorCreation?: boolean
      alwaysDisplayContentScoreDetails?: boolean
    }>
  ) => void
  getMatchedTerms: (terms: string[]) => Promise<{
    terms: string[]
    matches: Array<{
      term: string
      wordSegments: Array<{ index: number; originalSegment: string }>
    }>
  }>
}
```

### Options

Every option is optional; `anonymous` and `partner` shape the iframe URL, and each callback receives what the panel sends today.

Fields of `opts`:

| Option | Type | What it does |
| - | - | - |
| `anonymous` | `boolean` | Skips the session check: the panel treats the viewer as signed out and shows only the guidelines of a Content Editor already linked to the permalink. `false` by default; [Access and sharing](/integrations/embed/access-and-sharing) describes both modes. |
| `partner` | `string` | Names your integration; the panel records it with its usage events. Use one short identifier for your product; the value labels the integration and grants nothing. |
| `onNavigation` | `(view: string) => void` | Fires when the panel mounts and each time it moves between its four states; switching between the guidelines and score-only views does not fire it. `view` is `draft_loading`, `guidelines`, `draft_creation` (the dashboard), or `draft_not_found` (the connect screen); [What you see](/integrations/embed/quickstart#what-you-see) says when the panel shows each. |
| `onDraftLoaded` | `(permalink: string \| null) => void` | Fires with `null` as the panel mounts, then again for every permalink it resolves. `permalink` is the linked Content Editor's permalink, or `null` when none matched, so read it after a permalink has been set. |
| `onContentScoreChanged` | `(score: number) => void` | `score` is the SEO score, the search part of the Content Score, as a whole number from 1 to 100; the panel never sends zero, so the callback stays silent until the text earns its first point. The panel also computes an AI search score and a combined score, but the library forwards only the SEO number. |
| `onContentScoreDetailsChanged` | `(details: ContentScoreDetails) => void` | Fires with the breakdown behind the score, in the shape below. The shipped declaration lists a `score` field on it, which the panel does not send. |
| `onTermsToUseChanged` | `(termsToUse: object[]) => void` | Fires when the list of terms changes or any term's count in the HTML changes; each item is a term in the shape below. |
| `onFocusTermChanged` | `(focusTerm: object \| null) => void` | Fires when the writer highlights a term in the panel, moves to another, or clears the highlight; `focusTerm` is a term in the shape below, or `null`. |
| `onStructuralGuidelinesChanged` | `(guidelines: object[]) => void` | Fires, at most once per 300 ms, when the structure guidelines or the values measured for them change; each item is a structure guideline in the shape below. |

`onContentScoreDetailsChanged` receives a `ContentScoreDetails` object with this shape:

```ts theme={"system"}
{
  competitors_avg?: number
  competitors_max?: number
  details?: Array<{
    groupName: string
    value: number
    maxValue: number
    isNlp: boolean
  }>
}
```

* `competitors_avg` is the rounded average Content Score of the qualifying competitors.
* `competitors_max` is the highest Content Score among them, not rounded.
* A competitor qualifies when it is selected in the Content Editor, its analysis has completed, it is not excluded, and its score is above zero.
* Both fields are absent when no competitor qualifies.
* Each group in `details` is one part of the score.
* `value` is the points earned in that group and `maxValue` the points available.

Each item of `onTermsToUseChanged` is a term with this shape, and `onFocusTermChanged` receives one term of the same shape:

```ts theme={"system"}
{
  id: string
  item: string
  heading: boolean
  nlpEntity: boolean
  usage: {
    current: number
    optimal: [number, number]
    suboptimal: [number, number]
  }
}
```

* `item` is the term text.
* `heading` says whether the guidelines want the term in a heading.
* `nlpEntity` says whether the term comes from Surfer's entity analysis.
* `usage.current` is the term's count in the HTML.
* `usage.optimal` is the recommended count range.
* `usage.suboptimal` is the count range the panel flags as a warning.

Each item of `onStructuralGuidelinesChanged` is a structure guideline with this shape:

```ts theme={"system"}
{
  id?: string
  factor: 'wordCount' | 'headingsCount' | 'paragraphCount' | 'imgCount' | 'charCount'
  target: { min: number; max: number; avg: number; valueOverride?: number }
  value: number
  ranges: {
    STATUS_OK: [number, number]
    STATUS_ALERT: [number, number]
    STATUS_INFO: [number, number]
  }
  disabled?: boolean
}
```

* `value` is the count measured in the HTML.
* `target` is what the competitors use.
* `ranges` are the bands the panel grades the value against.

### The handle

The handle holds the iframe and six methods bound to it.

All but `getMatchedTerms` can be called before the iframe is attached or the panel has loaded. The library sends the call once the panel is ready. `getMatchedTerms` starts its timeout on the call, so it needs a loaded panel.

Members of the returned object:

| Member | What it does |
| - | - |
| `$iframe` | The panel's iframe, not yet in the DOM. Append it where the panel should sit and size it in your CSS; the library sets no size. |
| `setPermalink(permalink)` | Sets the permalink that identifies the Content Editor, in the form [Access and sharing](/integrations/embed/access-and-sharing) describes; `null` sends nothing. A value different from the previous one drops the HTML the library holds, so call `setHtml` again afterwards. |
| `setHtml(html)` | Holds the article HTML and delivers it once the panel reports a Content Editor loaded for the current permalink, so calling it early is safe. `null` clears the held HTML. |
| `requestView(view)` | Switches the panel between the full guidelines (`guidelines`) and the Content Score on its own (`content_score`). |
| `refreshDraft()` | Makes the panel reload the Content Editor from Surfer, for example after you linked one to the permalink through the API. |
| `configureView(config)` | Applies the view flags below. Each call replaces the whole configuration: a flag left out of the call turns off. |
| `getMatchedTerms(terms)` | Asks the panel where each of `terms` occurs in the HTML it holds. The promise resolves with `terms` and one `matches` entry per occurrence, and rejects with the string `Timeout` after 1000 ms. |

Each word segment in a `matches` entry gives the word's `index` in the held HTML. At runtime it also carries `segment`, `isWordLike`, and `breakType`, while the declaration file lists only `index` and `originalSegment`.

### View flags

Flags `configureView` accepts, each `false` unless set:

| Flag | Effect when `true` |
| - | - |
| `disableBatchContentEditorCreation` | Removes the batch option from the create form in the dashboard the panel shows while no Content Editor is linked. |
| `alwaysDisplayContentScoreDetails` | Keeps the score breakdown open in the `content_score` view when the Content Editor has one. It does not add a breakdown where the panel shows none. |

### Example

Take `configureView`, `requestView`, and `getMatchedTerms` from the handle the [quickstart](/integrations/embed/quickstart) creates with `initWithOptions`. Run after the quickstart's `setHtml` call, this code hides the batch option in the dashboard, shows the Content Score on its own, and finds two terms in the HTML the panel holds:

```js theme={"system"}
configureView({ disableBatchContentEditorCreation: true })
requestView('content_score')

getMatchedTerms(['content score', 'guidelines'])
  .then(({ matches }) => console.log(matches))
  .catch(error => console.log(error))
```

`configureView` and `requestView` are held until the panel is ready. `getMatchedTerms` searches the HTML the library has delivered, which happens once a Content Editor has loaded, so call it after `onDraftLoaded` has received a permalink; when the panel does not answer within 1000 ms the promise rejects with `Timeout`.

## Behavior to plan for

Four behaviors of the library decide how a host is built.

* There is no dispose method. The library keeps a message listener on `window` for the life of the page. Mount once per page, reuse the handle, and change Content Editors with `setPermalink`.
* A score of zero is not emitted, and neither is its breakdown. Treat "no callback yet" as no score.
* Callbacks are asynchronous. The panel re-scores after each `setHtml` whose HTML differs from the last. The score callbacks fire when that finishes, never on your call stack. The panel holds no timer of its own, so the delay is the scoring.
* The panel signs in with the writer's Surfer session, through the browser's cookies for Surfer. [Access and sharing](/integrations/embed/access-and-sharing) covers the modes and the cookie requirements.

## Versioning

`surfer_guidelines_1_x_x.js` is the current 1.x build; a pinned file freezes the library, not the panel.

* Pinned builds are named `surfer_guidelines_1_<minor>_<patch>.js`; the newest is `surfer_guidelines_1_3_1.js`, and today `1_x_x` serves the same file.
* The panel is part of the Surfer app and updates on its own schedule whichever file you load. A pinned build keeps the library behavior of that release, including which callbacks and handle members it has.
* The library accepts messages from any 1.x panel and the panel from any 1.x library.


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