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

# Embed the Dashboard

> Embed the Answering Agent dashboard into your website using secure embed tokens.

<Note>Embedding the dashboard is available to enterprise customers with Partner API access. Contact us to get started.</Note>

## Summary/Quick Start

1. **Get your API key** from Settings → API Keys in your Answering Agent dashboard
2. **Create a user** via `POST /api/v1/users`, or attach an existing partner identity to an approved linked organization
3. **Request an embed token** for the intended `organization_id` and keep it server-side until rendering
4. **Embed the dashboard** using `<iframe src="https://answeringagent.com/dashboard/embed?token=EMBED_TOKEN">`
5. **User's browser** loads embed → token is auto-exchanged for dashboard auth

`X-API-KEY` = your server only. `embed_token` = the compact browser input for one intended dashboard context.

***

The Answering Agent dashboard can be embedded into your website, providing a seamless customer support platform for your users. The embed system uses secure, compact tokens that authenticate users without exposing sensitive credentials.

## Authentication Overview

The embed system involves **two different authentication contexts**:

<CardGroup cols={2}>
  <Card title="Your Server (Partner)" icon="server">
    Uses the **X-API-KEY** header to call the Partner API and retrieve embed tokens for your users.
  </Card>

  <Card title="User's Browser (End User)" icon="browser">
    Uses the **embed\_token** which is automatically exchanged for embedded dashboard authentication.
  </Card>
</CardGroup>

<Warning>
  Never expose your `X-API-KEY` in client-side code. The API key is for server-to-server communication only. End users only need the `embed_token`.
</Warning>

## How Tokens Flow

<Steps>
  <Step title="Your server requests an embed token">
    Call the Partner API with your `X-API-KEY` to get an `embed_token` for a specific user and, for scoped viewers, a specific `organization_id`.
  </Step>

  <Step title="Your server passes the token to the browser">
    Inject the `embed_token` into the iframe URL.
  </Step>

  <Step title="The embed exchanges the token">
    When the iframe loads, Answering Agent exchanges the `embed_token` for embedded dashboard authentication.
  </Step>

  <Step title="User is authenticated">
    The embedded dashboard uses that authentication for page loads, navigation, and dashboard API calls. This happens automatically.
  </Step>
</Steps>

## Getting Embed Tokens (Server-Side)

<Note>
  These endpoints require your **X-API-KEY** header and should only be called from your server, never from client-side code.
</Note>

### From User Creation

When you [create a user](/v1/users) from your server, the response includes an `embed_token`:

```json theme={null}
{
  "embed_token": "MTIzNDV8ZDgyZDk3Mzg1OTAxNTkzNA",
  "user": { /* user details */ },
  "team": { /* team details */ },
  "locations": [ /* locations */ ]
}
```

### Managing Existing Tokens

For existing users, your server can retrieve or regenerate embed tokens. Include `organization_id` for scoped viewers and whenever a user has more than one eligible organization context:

#### Get Current Embed Token

```bash theme={null}
GET /api/v1/users/{external_id}/embed-token?organization_id={organization_id}
```

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    # Called from YOUR SERVER, not the browser
    curl -H "X-API-KEY: your-api-key" \
         "https://answeringagent.com/api/v1/users/your-user-123/embed-token?organization_id=456"
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    // Server-side code (Express, Next.js API route, etc.)
    const response = await fetch(
      'https://answeringagent.com/api/v1/users/your-user-123/embed-token?organization_id=456',
      {
        headers: {
          'X-API-KEY': process.env.ANSWERING_AGENT_API_KEY // Never expose this!
        }
      }
    );
    const { embed_token } = await response.json();
    // Pass embed_token to your frontend template
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    // Server-side code (Laravel, etc.)
    $response = Http::withHeaders([
        'X-API-KEY' => config('services.answering_agent.api_key'),
    ])->get('https://answeringagent.com/api/v1/users/your-user-123/embed-token', [
        'organization_id' => 456,
    ]);

    $embedToken = $response->json('embed_token');
    // Pass $embedToken to your view
    ```
  </Tab>
</Tabs>

**Response:**

```json theme={null}
{
  "embed_token": "MTIzNDV8ZDgyZDk3Mzg1OTAxNTkzNA",
  "user_id": "your-user-123",
  "organization_id": 456
}
```

#### Generate New Embed Token

```bash theme={null}
POST /api/v1/users/{external_id}/embed-token?organization_id={organization_id}
```

<Warning>
  For an organization-scoped viewer, rotation invalidates only that viewer relationship. Other approved organizations for the same identity keep working. Partner-provisioned users retain the legacy user-wide signing authority.

  For a partner-provisioned user, the signing authority remains user-wide even when `organization_id` selects the returned token's team. The response includes `"rotation_scope": "user"`, and every previously issued provisioned-organization compact token for that user must be replaced.
</Warning>

```bash theme={null}
# Called from YOUR SERVER
curl -X POST \
     -H "X-API-KEY: your-api-key" \
     "https://answeringagent.com/api/v1/users/your-user-123/embed-token?organization_id=456"
```

**Response:**

```json theme={null}
{
  "embed_token": "OTg3NjV8YWJjZGVmZ2hpams1Njc4OTA",
  "user_id": "your-user-123",
  "organization_id": 456,
  "message": "New organization-scoped embed token generated successfully"
}
```

### Multi-Organization Viewer Setup

An owner-linked organization must independently approve your partner account before you can attach viewers. Your server then reuses one partner-owned identity across approved organizations:

```bash theme={null}
# Attach the same identity to two independently approved organizations
curl -X PUT -H "X-API-KEY: your-api-key" \
  https://answeringagent.com/api/v1/organizations/456/dashboard-viewers/account-manager-42

curl -X PUT -H "X-API-KEY: your-api-key" \
  https://answeringagent.com/api/v1/organizations/789/dashboard-viewers/account-manager-42

# Select one organization when issuing the iframe token
curl -H "X-API-KEY: your-api-key" \
  "https://answeringagent.com/api/v1/users/account-manager-42/embed-token?organization_id=789"
```

The scoped viewer can read all current and future locations in the selected organization. It cannot change settings, manage roles or members, call mutation endpoints, subscribe to operator realtime channels, or initiate recurring AI/model work.

## Embedding the Dashboard

Embed the dashboard with an iframe for complete DOM isolation:

```html theme={null}
<iframe
  src="https://answeringagent.com/dashboard/embed?token=MTIzNDV8ZDgyZDk3Mzg1OTAxNTkzNA"
  width="100%"
  height="600px"
  frameborder="0"
  style="border: none;">
</iframe>
```

**Benefits:**

* Zero JavaScript required - just paste the iFrame
* Complete DOM isolation from your page
* Works with strict Content Security Policies
* No conflicts with your site's CSS or JavaScript
* Easy to implement in any CMS or page builder

**Partner theme:**

```html theme={null}
<iframe
  src="https://answeringagent.com/dashboard/embed?token=YOUR_TOKEN&theme=sonnys"
  width="100%"
  height="600px"
  frameborder="0">
</iframe>
```

Supported theme values are `sonnys` and `optspot`. Omit `theme` for the default Answering Agent dashboard styling.

Existing iframe embeds that use `https://answeringagent.com/dashboard.html?token=...` remain supported and run through the same embed exchange. Existing script embeds that load `https://answeringagent.com/embed.js` also remain supported; the script creates a `/dashboard/embed` iframe for you. New installs should use the direct `/dashboard/embed` iframe.

### Quick Start Checklist

1. **iFrame tag** with `src` pointing to `/dashboard/embed?token=YOUR_TOKEN`
2. **Valid embed token** in the URL query parameter
3. **Optional partner theme** with `theme=sonnys` or `theme=optspot`

That's it. The iframe route automatically exchanges the `embed_token` for dashboard authentication and the dashboard enforces the selected organization scope.

When switching organizations, request a token for the new `organization_id` and replace the existing iframe `src`. Running multiple same-origin organization embeds simultaneously in one browser session is not a supported isolation boundary.

Custom API URLs are restricted to the default API origin, the playground API origin, or origins listed in the Next.js `EMBED_API_URL_ALLOWLIST` environment variable.

## Dynamic Token Integration

For production applications, your **server** should fetch embed tokens and pass them to the frontend.

### Server-Side Endpoint

Create an endpoint in your backend that fetches the embed token:

<Tabs>
  <Tab title="Node.js / Express">
    ```javascript theme={null}
    // routes/embed.js - YOUR SERVER
    app.get('/api/embed-token', async (req, res) => {
      // 1. Authenticate your own user (your auth system)
      const user = await authenticateRequest(req);
      if (!user) return res.status(401).json({ error: 'Unauthorized' });

      // 2. Get the user's external ID (their ID in Answering Agent)
      const externalId = user.answeringAgentId;
      const organizationId = authorizeOrganization(req, user);

      // 3. Fetch embed token from Answering Agent using YOUR API key
      const response = await fetch(
        `https://answeringagent.com/api/v1/users/${externalId}/embed-token?organization_id=${organizationId}`,
        {
          headers: {
            'X-API-KEY': process.env.ANSWERING_AGENT_API_KEY
          }
        }
      );

      const { embed_token } = await response.json();
      res.json({ embed_token });
    });
    ```
  </Tab>

  <Tab title="PHP / Laravel">
    ```php theme={null}
    // routes/api.php - YOUR SERVER
    Route::get('/embed-token', function (Request $request) {
        // 1. Authenticate your own user (your auth system)
        $user = $request->user();

        // 2. Get the user's external ID (their ID in Answering Agent)
        $externalId = $user->answering_agent_id;
        $organizationId = authorizeOrganization($request, $user);

        // 3. Fetch embed token from Answering Agent using YOUR API key
        $response = Http::withHeaders([
            'X-API-KEY' => config('services.answering_agent.api_key'),
        ])->get("https://answeringagent.com/api/v1/users/{$externalId}/embed-token", [
            'organization_id' => $organizationId,
        ]);

        return response()->json([
            'embed_token' => $response->json('embed_token'),
        ]);
    })->middleware('auth');
    ```
  </Tab>
</Tabs>

### Client-Side Integration

Your frontend calls **your backend** (not Answering Agent directly):

```javascript theme={null}
async function loadAnsweringAgentDashboard() {
  try {
    // Call YOUR backend endpoint (which uses X-API-KEY internally)
    const response = await fetch('/api/embed-token', {
      headers: {
        'Authorization': `Bearer ${yourSessionToken}` // Your auth
      }
    });

    const { embed_token } = await response.json();

    // Set the iframe URL. The iframe handles token exchange automatically.
    const frame = document.getElementById('answering-agent-dashboard');
    frame.src = `https://answeringagent.com/dashboard/embed?token=${encodeURIComponent(embed_token)}`;
  } catch (error) {
    console.error('Failed to load dashboard:', error);
  }
}
```

<Info>
  The iframe route automatically handles exchanging the `embed_token` for dashboard auth. You don't need to call `/api/validate-embed-token` yourself.
</Info>

### React Component Example

```jsx theme={null}
function AnsweringAgentDashboard({ organizationId }) {
  const [embedToken, setEmbedToken] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Call YOUR backend (which calls Partner API with X-API-KEY)
    fetch(`/api/embed-token?organization_id=${encodeURIComponent(organizationId)}`)
      .then(res => res.json())
      .then(data => setEmbedToken(data.embed_token))
      .catch(err => setError(err.message));
  }, [organizationId]);

  if (error) return <div>Failed to load dashboard</div>;
  if (!embedToken) return <div>Loading...</div>;

  return (
    <iframe
      id="answering-agent-dashboard"
      src={`https://answeringagent.com/dashboard/embed?token=${encodeURIComponent(embedToken)}`}
      style={{ width: '100%', height: '600px', border: 0 }}
    />
  );
}
```

<Note>
  Your `/api/embed-token` endpoint should authenticate the request using your own auth system, then call the Answering Agent Partner API with your `X-API-KEY`.
</Note>

## Security Features

### Token Types

| Token            | Who Uses It                   | How It's Sent                       | How to Get It                    |
| ---------------- | ----------------------------- | ----------------------------------- | -------------------------------- |
| **X-API-KEY**    | Your server                   | `X-API-KEY` header                  | Dashboard → Settings → API Keys  |
| **embed\_token** | User's browser                | URL param                           | Partner API response             |
| **auth\_token**  | Answering Agent embed runtime | Server-only bearer auth for iframes | Auto-exchanged from embed\_token |

### Compact Tokens

* URL-safe base64 encoding without padding
* Optimized for query parameters and data attributes

### Secure Validation

* Uses HMAC-SHA256 for cryptographic integrity
* Scoped tokens are tied to a specific user, partner, organization, grant, and authority generation
* Every scoped request revalidates current authority before it can read organization data

### Token Lifecycle

* Compact tokens have no built-in expiry but can be invalidated immediately
* Exchanged dashboard bearers expire after 15 minutes
* Scoped viewers can have independent tokens for multiple organizations
* Detach, grant disable/revoke, partner-link revoke, membership removal, or user deletion invalidates the affected scoped authority

## Error Handling

### Invalid or Expired Tokens

If an embed token is invalid, the embedded dashboard will show an authentication error. Common causes:

* Token was invalidated by generating a new one
* The viewer was detached or its owner-approved grant, partner link, membership, or user was revoked
* Token is malformed or corrupted
* User account was deleted or suspended

### Missing Configuration

If the embed container is not properly configured, you may see:

* Console warnings about missing configuration
* Dashboard not loading or appearing blank
* Authentication errors

## Best Practices

### Server-Side (Your Backend)

1. **Never expose X-API-KEY** - Keep your API key in server-only secret storage
2. **Proxy embed tokens** - Your backend should fetch tokens and pass them to the frontend
3. **Bind requests to organization\_id** - Authorize the organization in your own backend before requesting its scoped token
4. **Replace the iframe when switching** - Request a fresh organization token and update the single iframe `src`
5. **Rotate on security events** - Rotate only the affected organization relationship when possible

### Client-Side (User's Browser)

1. **Use iframe embeds** - Use `/dashboard/embed?token=...` for the Next.js dashboard experience
2. **Handle errors gracefully** - Show fallback UI if embedding fails
3. **Use HTTPS only** - Never transmit tokens over insecure connections

## Troubleshooting

### Dashboard Won't Load

* Verify the embed token is valid by checking the Partner API response
* Confirm the iframe `src` includes `/dashboard/embed?token=...`
* Check browser console for JavaScript errors
* Ensure the iframe has proper dimensions and is visible

### Authentication Errors

* The embed token may have been invalidated - fetch a new one from the Partner API
* For a multi-organization user, verify your server requested the intended `organization_id`
* Check that your server is using the correct `X-API-KEY`
* Verify the user exists in Answering Agent
* Ensure the iframe URL includes a valid `token` parameter

### Configuration Errors

* Verify the iframe `src` points to `https://answeringagent.com/dashboard/embed`
* Check that the `token` query parameter contains a valid embed token
* Monitor network requests to verify API calls are reaching the correct endpoint

### Performance Issues

* Cache embed tokens in your backend to avoid repeated Partner API calls
* Monitor iframe sizing to prevent layout shifts
