> For the complete documentation index, see [llms.txt](https://docs.insurgrid.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.insurgrid.com/guides/embed-loss-runs.md).

# Embed Loss Runs

Run the Loss Runs workflow inside your product, with each of your users seeing only their own requests.

Requesting loss runs means chasing carriers and waiting. This embed puts that whole workflow inside your own product, so your users can raise a request and watch it progress without leaving your app — and without seeing anyone else's requests.

They don't need InsurGrid accounts. You tell InsurGrid which of your users is looking, and it keeps their histories apart.

**What you'll need**

* An InsurGrid account with Loss Runs enabled. If it isn't, the embed replies `403 feature_disabled` and nothing loads.
* An **API key** belonging to an agency admin ([where to get one](/get-started/03-api-credentials.md)), kept on your server.
* A page in your product that only signed-in users can reach.

|              | Staging                                   | Production                        |
| ------------ | ----------------------------------------- | --------------------------------- |
| Embed script | `https://dashboard-staging.insurgrid.com` | `https://dashboard.insurgrid.com` |
| API          | `https://phoenix-staging.insurgrid.com`   | `https://phoenix.insurgrid.com`   |

Only the host changes between them — see [Environments](/guides/environments.md).

***

## How it fits together

Three pieces, and it helps to know which is which:

1. **A snippet** you paste into your page. It draws the Loss Runs screen in a frame.
2. **A route on your own server** that hands that frame a short-lived pass.
3. **InsurGrid's API**, which your server calls to create the pass.

The browser never sees your API key — only a pass that expires after about 15 minutes and works for one of your users and nothing else.

> **New term — a pass (we call it an embed session).** A short string that stands in for "this particular signed-in user of yours, for the next few minutes". It carries no account details and can't do anything outside Loss Runs for that one user.

***

## 1. Paste the snippet into your page

Copy it from the **Embed** button on your Loss Runs dashboard, or use this:

```html
<div id="insurgrid-loss-runs"></div>
<script
  src="https://dashboard-staging.insurgrid.com/embed.js"
  data-product="loss-runs"
  data-token-url="/insurgrid-loss-runs-token">
</script>
```

Swap the host for `dashboard.insurgrid.com` in production.

That's the whole front-end job. The script finds the empty `<div>`, puts a frame inside it, and asks your `data-token-url` route for a pass. The frame grows on its own as the content changes, so there's nothing to measure.

| Attribute         | Required | What it does                                                               |
| ----------------- | -------- | -------------------------------------------------------------------------- |
| `data-product`    | yes      | Must be `loss-runs`. Leave it off and you get the forms embed instead.     |
| `data-token-url`  | yes      | The route on **your** server that returns a pass. Built next.              |
| `data-target`     | no       | A CSS selector, if you'd rather not use `#insurgrid-loss-runs`.            |
| `data-min-height` | no       | Starting height in pixels. Defaults to 640; anything under 320 is ignored. |

The script fetches `data-token-url` with same-origin credentials, so the route must live on the **same origin as the page**. A route on another domain receives no login cookie.

## 2. Add the token route to your server

When the script asks for a pass, the browser sends your own login cookie with the request — which is how your route knows who's asking. Put it behind the same login check as the rest of your app.

```js
app.get('/insurgrid-loss-runs-token', requireCustomerLogin, async (req, res, next) => {
  try {
    const response = await fetch(`${process.env.INSURGRID_API_ORIGIN}/api/v1/embed-sessions`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.INSURGRID_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        externalUserId: String(req.user.id),
        product: 'loss_runs',
      }),
    });
    res.set('Cache-Control', 'no-store');
    res.status(response.status).json(await response.json());
  } catch (error) {
    next(error);
  }
});
```

Set `INSURGRID_API_ORIGIN` to the staging host while you build, production when you ship. Pass InsurGrid's reply straight back, unchanged:

```json
{ "token": "emb_8cPq…", "expiresAt": "2026-10-07T13:11:36Z" }
```

Four things that matter:

* **`product` is `loss_runs` here — with an underscore** — while the HTML attribute is `loss-runs` with a hyphen. Easy to mix up.
* **Send `Cache-Control: no-store`**, so no browser or proxy keeps a copy of someone's pass and hands it to the next person.
* **Your API key never leaves the server.** Not in the snippet, page scripts, a URL, or anything you return to the browser.
* **A staging key works only against staging.** Pointed at production it returns `401`.

> ⚠️ **Don't let the browser choose whose data it sees.** Take `externalUserId` from your own login session, never from a query string or form field. InsurGrid rejects a request that tries to set it from the browser, but the safe habit is not to read it from there at all.

## 3. Pick a stable id for each of your users

`externalUserId` is how InsurGrid keeps your users apart. Whatever you send is remembered: send the same value next week and that person sees the loss runs they requested today.

* Use something that never changes for a person — your own database id is ideal.
* **Capitalisation counts.** `User-42` and `user-42` are two different people.
* Don't reuse an id across people, and avoid anything that changes, like an email address they might update.

Each of your users sees only their own requests.

## 4. React to what happens in the frame

Optional, but useful for refreshing your own screen or recording the event. Listen on the element you pointed the snippet at:

```js
const mount = document.querySelector('#insurgrid-loss-runs');

mount.addEventListener('insurgrid:loss-run-created', (event) => {
  console.log('New request', event.detail.requestId);
});
```

| Event                        | Raised when                                  | `event.detail`  |
| ---------------------------- | -------------------------------------------- | --------------- |
| `insurgrid:ready`            | The embed has loaded and been handed a pass. | —               |
| `insurgrid:loss-run-created` | Your user finished creating a request.       | `{ requestId }` |
| `insurgrid:close`            | Your user backed out of a screen.            | —               |
| `insurgrid:error`            | Something went wrong — codes below.          | `{ code }`      |

These are for your interface only — a toast, a counter, a refresh. The Loss Runs screen itself is the record of what happened.

## 5. Refreshing, and what happens at sign-out

A pass lasts about 15 minutes, and roughly a minute before it runs out the script quietly calls your token route again. Your user notices nothing, even with the page open all afternoon. There's nothing for you to schedule.

When your user signs out of **your** app, your token route should stop issuing passes — but be aware of what that does and doesn't do:

* **The pass already in the frame keeps working until it expires**, up to 15 minutes. There is no way to revoke one early.
* **The script keeps retrying your route in the background**, backing off to once every 30 seconds. It doesn't give up, and it doesn't show an expiry message — the embed only surfaces an error once the pass has expired and a call fails.

So if you need the embed to stop the moment someone signs out, take it off the page yourself: navigate away, or — if you mounted it with `InsurGrid.mount` rather than the snippet — call `destroy()` on the handle it returned.

***

## When something doesn't work

| What you see                             | Usually means                                                                                                         |
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| "Your session expired. Reload the page." | The **first** call to your token route failed. Check you're signed in and that the path matches `data-token-url`.     |
| `insurgrid:error` with `session_expired` | The same cause, reported to your code so you can handle it yourself.                                                  |
| `403 feature_disabled`                   | Loss Runs isn't enabled for the agent who owns the API key. Contact InsurGrid.                                        |
| `401`                                    | The key is wrong, sent without `Bearer`, belongs to the other environment, or isn't an agency admin's.                |
| `422` mentioning `product`               | `product` must be exactly `forms` or `loss_runs`. Check the underscore.                                               |
| The frame stays blank                    | Open your browser's network tab and look at the call to your token route — it's almost always a `401` or `404` there. |

## What stays private

* The pass is handed to the frame behind the scenes, never put in its web address — so it won't turn up in browser history, a shared link, or your server logs.
* Each pass is tied to one of your users, and everything it can do — listing, creating, authorizing, downloading — is scoped to that person's requests. Treat it like any other bearer credential: within its 15 minutes, whoever holds it can act as that user.
* Your users can't browse your client list through the embed. The insured name is a plain text box there, not the lookup your own staff get in the dashboard.
* Documents are downloaded through short-lived signed links, not public ones.

## Related

* [Environments](/guides/environments.md) — staging and production addresses.
* [Get your API credentials](/get-started/03-api-credentials.md)
* [The two embeds](/guides/embeds.md) — the equivalent for intake forms.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.insurgrid.com/guides/embed-loss-runs.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
