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

# Embeddable Request Log

> Embed an ApiTraffic request log directly into your application — like Stripe Elements for API traffic.

Give your customers or internal teams a real-time view of API traffic without leaving your app. The embed SDK renders a fully styled, CSS-isolated request log using Web Components (Shadow DOM) — no iFrames, no style conflicts.

<Note>
  The embed feature requires an **ApiTraffic API token** on your server to generate signed embed tokens. The client-side SDK never sees your API token.
</Note>

## How It Works

```mermaid theme={null}
sequenceDiagram
    participant Browser as Client Browser
    participant Server as Your Server
    participant AT as ApiTraffic API

    Browser->>Server: GET /api/embed-token
    Server->>AT: POST /v1/accounts/{id}/embeds/token
    AT-->>Server: { token, expiresAt }
    Server-->>Browser: { token }
    Browser->>AT: GET /v1/embed/requests (Bearer token)
    AT-->>Browser: Request log data
```

1. Your **backend** calls the ApiTraffic API with your API token to generate a signed embed JWT.
2. The JWT locks in the bucket, filters, and theme — the client SDK cannot tamper with these.
3. Your **frontend** passes the JWT to the `<apitraffic-request-log>` Web Component.
4. The component fetches data directly from ApiTraffic's embed endpoints using the JWT.

## Generate an Embed Token (Server-Side)

<CodeGroup>
  ```javascript Node.js theme={null}
  const axios = require('axios');

  async function getEmbedToken(accountSid, apiToken) {
    const response = await axios.post(
      `https://api.apitraffic.io/v1/accounts/${accountSid}/embeds/token`,
      {
        bucketSid: 'your-bucket-sid',
        view: 'request-log',
        filters: {
          environmentSid: 'abc123',              // optional
          criteria: 'method == "GET"',           // optional — same filter syntax as the dashboard
          streamViewSid: 'sv_...'                // optional — use a saved stream view
        },
        ttl: 3600,                               // seconds (min 60, max 86400)
        theme: 'light'                           // 'light' | 'dark' | 'auto'
      },
      {
        headers: { Authorization: `Bearer ${apiToken}` }
      }
    );

    return response.data; // { token, expiresAt }
  }
  ```

  ```python Python theme={null}
  import requests

  def get_embed_token(account_sid, api_token):
      response = requests.post(
          f"https://api.apitraffic.io/v1/accounts/{account_sid}/embeds/token",
          json={
              "bucketSid": "your-bucket-sid",
              "view": "request-log",
              "filters": {
                  "environmentSid": "abc123",
                  "criteria": 'method == "GET"'
              },
              "ttl": 3600,
              "theme": "light"
          },
          headers={"Authorization": f"Bearer {api_token}"}
      )
      return response.json()  # { "token": "...", "expiresAt": "..." }
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.apitraffic.io/v1/accounts/acc_.../embeds/token" \
    -H "Authorization: Bearer YOUR_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "bucketSid": "your-bucket-sid",
      "filters": { "criteria": "method == \"GET\"" },
      "ttl": 3600,
      "theme": "light"
    }'
  ```
</CodeGroup>

### Token Parameters

| Parameter   | Type     | Required | Default         | Description                                         |
| ----------- | -------- | -------- | --------------- | --------------------------------------------------- |
| `bucketSid` | `string` | Yes      | —               | The bucket to scope the embed to.                   |
| `view`      | `string` | No       | `'request-log'` | The embed view type.                                |
| `filters`   | `object` | No       | `{}`            | Filters locked into the token (see below).          |
| `ttl`       | `number` | No       | `3600`          | Token lifetime in seconds (60–86400).               |
| `theme`     | `string` | No       | `'light'`       | Theme preference: `'light'`, `'dark'`, or `'auto'`. |

### Filter Options

| Filter           | Type     | Description                                      |
| ---------------- | -------- | ------------------------------------------------ |
| `environmentSid` | `string` | Filter to a specific environment.                |
| `criteria`       | `string` | Search criteria string (same syntax as the API). |
| `streamViewSid`  | `string` | Use a saved stream view's filter criteria.       |

<Warning>
  Filters are **server-side only**. They are cryptographically locked into the JWT — the client SDK cannot modify, add, or remove filters. This prevents data leakage.
</Warning>

***

## Vanilla JavaScript

### Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install @apitraffic/embed
  ```

  ```bash yarn theme={null}
  yarn add @apitraffic/embed
  ```

  ```bash CDN (no build step) theme={null}
  <script src="https://unpkg.com/@apitraffic/embed/dist/apitraffic-embed.iife.js"></script>
  ```
</CodeGroup>

### Basic Usage

```html theme={null}
<!-- CDN approach -->
<script src="https://unpkg.com/@apitraffic/embed/dist/apitraffic-embed.iife.js"></script>

<apitraffic-request-log id="log" theme="light"></apitraffic-request-log>

<script>
  // Fetch a token from your backend
  fetch('/api/embed-token')
    .then(res => res.json())
    .then(({ token }) => {
      document.getElementById('log').token = token;
    });
</script>
```

Or with ES module imports:

```javascript theme={null}
import '@apitraffic/embed';

const el = document.querySelector('apitraffic-request-log');
el.token = await getTokenFromYourBackend();
```

### Attributes

| Attribute     | Type     | Default                          | Description                                |
| ------------- | -------- | -------------------------------- | ------------------------------------------ |
| `token`       | `string` | —                                | **Required.** The signed embed JWT.        |
| `theme`       | `string` | `'light'`                        | `'light'`, `'dark'`, or `'auto'`.          |
| `api-url`     | `string` | `'https://api.apitraffic.io/v1'` | Override the API base URL (self-hosted).   |
| `page-size`   | `number` | `25`                             | Requests per page (1–100).                 |
| `show-detail` | `string` | `'true'`                         | Set to `'false'` to disable row expansion. |

### Events

```javascript theme={null}
const el = document.querySelector('apitraffic-request-log');

// Fires after the first successful data load
el.addEventListener('apitraffic:ready', () => {
  console.log('Request log is ready');
});

// Fires on any API error
el.addEventListener('apitraffic:error', (e) => {
  console.error('Error:', e.detail.message);
});

// Fires ~30 seconds before the token expires
el.addEventListener('apitraffic:token-expired', async () => {
  const { token } = await fetch('/api/embed-token').then(r => r.json());
  el.token = token; // component re-fetches automatically
});

// Fires when a request row is clicked
el.addEventListener('apitraffic:request-click', (e) => {
  console.log('Clicked request:', e.detail.requestSid);
});
```

***

## React

### Installation

```bash theme={null}
npm install @apitraffic/embed-react @apitraffic/embed
```

### Drop-In Component

```jsx theme={null}
import { RequestLog } from '@apitraffic/embed-react';

function ApiLog({ token }) {
  return (
    <RequestLog
      token={token}
      theme="light"
      pageSize={25}
      onTokenExpired={async () => {
        const res = await fetch('/api/embed-token');
        const { token } = await res.json();
        return token; // returning the token auto-refreshes
      }}
      onReady={() => console.log('Loaded')}
      onError={(e) => console.error(e.detail)}
      onRequestClick={(e) => console.log(e.detail.requestSid)}
    />
  );
}
```

### Props

| Prop             | Type       | Default   | Description                                |
| ---------------- | ---------- | --------- | ------------------------------------------ |
| `token`          | `string`   | —         | **Required.** The signed embed JWT.        |
| `theme`          | `string`   | `'light'` | `'light'`, `'dark'`, or `'auto'`.          |
| `apiUrl`         | `string`   | —         | Override the API base URL.                 |
| `pageSize`       | `number`   | `25`      | Requests per page.                         |
| `showDetail`     | `boolean`  | `true`    | Enable/disable expandable row detail.      |
| `onTokenExpired` | `function` | —         | Return a new token string to auto-refresh. |
| `onReady`        | `function` | —         | Fires after the first successful load.     |
| `onError`        | `function` | —         | Fires on API errors.                       |
| `onRequestClick` | `function` | —         | Fires when a row is clicked.               |

### Headless Hook

Use `useApiTrafficEmbed` when you want full control over rendering:

```jsx theme={null}
import { useApiTrafficEmbed } from '@apitraffic/embed-react';

function CustomLog({ token }) {
  const {
    requests,
    loading,
    loadingMore,
    error,
    hasMore,
    loadMore,
    refresh
  } = useApiTrafficEmbed({
    token,
    pageSize: 50,
    onTokenExpired: async () => {
      const res = await fetch('/api/embed-token');
      return (await res.json()).token;
    }
  });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <div>
      <button onClick={refresh}>Refresh</button>
      {requests.map(req => (
        <div key={req.sid}>
          {req.request?.method} {req.request?.path} — {req.response?.statusCode}
        </div>
      ))}
      {hasMore && (
        <button onClick={loadMore} disabled={loadingMore}>
          {loadingMore ? 'Loading...' : 'Load More'}
        </button>
      )}
    </div>
  );
}
```

***

## Theming

The Web Component uses CSS custom properties for theming. Override them on the host element:

```css theme={null}
apitraffic-request-log {
  /* Typography */
  --at-font-family: 'Inter', sans-serif;
  --at-font-size: 13px;

  /* Colors */
  --at-bg-color: #ffffff;
  --at-text-color: #1f2937;
  --at-text-muted: #6b7280;
  --at-accent-color: #3b82f6;
  --at-danger-color: #ef4444;

  /* Layout */
  --at-border-color: #e5e7eb;
  --at-border-radius: 6px;
  --at-row-hover-bg: #f9fafb;
  --at-max-height: 600px;
}
```

### Available CSS Custom Properties

| Property             | Default           | Description                    |
| -------------------- | ----------------- | ------------------------------ |
| `--at-font-family`   | System font stack | Font family                    |
| `--at-font-size`     | `13px`            | Base font size                 |
| `--at-bg-color`      | `#ffffff`         | Container background           |
| `--at-text-color`    | `#1f2937`         | Primary text color             |
| `--at-text-muted`    | `#6b7280`         | Secondary/muted text           |
| `--at-accent-color`  | `#3b82f6`         | Links, buttons, GET method     |
| `--at-danger-color`  | `#ef4444`         | Errors, DELETE method          |
| `--at-border-color`  | `#e5e7eb`         | Border color                   |
| `--at-border-radius` | `6px`             | Container border radius        |
| `--at-row-hover-bg`  | `#f9fafb`         | Row hover background           |
| `--at-max-height`    | `none`            | Max height (set for scrolling) |

### Dark Mode

Set `theme="dark"` for a built-in dark theme, or `theme="auto"` to follow the user's OS preference via `prefers-color-scheme`.

***

## Token Refresh Flow

Embed tokens are short-lived by design. The SDK fires a `token-expired` event approximately 30 seconds before expiry, giving your app time to fetch a fresh token seamlessly:

```mermaid theme={null}
sequenceDiagram
    participant Component as Embed Component
    participant App as Your App
    participant Server as Your Server
    participant AT as ApiTraffic API

    Note over Component: Token expires in 30s
    Component->>App: apitraffic:token-expired event
    App->>Server: GET /api/embed-token
    Server->>AT: POST .../embeds/token
    AT-->>Server: { token, expiresAt }
    Server-->>App: { token }
    App->>Component: el.token = newToken
    Note over Component: Re-fetches data with new token
```

***

## Security

<AccordionGroup>
  <Accordion title="How are filters protected?">
    All filters (bucket, environment, criteria, stream view) are cryptographically signed into the JWT using HMAC-SHA256. The client SDK reads the token to display data but cannot modify the filters. Any tampering invalidates the signature.
  </Accordion>

  <Accordion title="Can the client access data outside the filter scope?">
    No. The embed data endpoints extract the `accountSid`, `bucketSid`, and all filters directly from the verified JWT payload. Query parameters from the client are ignored for filtering — only pagination cursors (`from`, `limit`) are accepted.
  </Accordion>

  <Accordion title="What happens if a token is stolen?">
    Tokens are short-lived (default 1 hour, max 24 hours) and scoped to a specific bucket and filter set. An attacker can only see the same data the embed was designed to show, and only until the token expires. You can also rotate the signing secret by calling the token endpoint — a new secret is auto-generated if the existing one is cleared.
  </Accordion>

  <Accordion title="Is my API token exposed to the browser?">
    No. Your API token is only used **server-side** to generate embed JWTs. The browser only ever sees the embed JWT, which cannot be used to access any other ApiTraffic API endpoints.
  </Accordion>
</AccordionGroup>

***

## API Reference

### Create Embed Token

```
POST /v1/accounts/{accountSid}/embeds/token
```

**Authentication:** Bearer token (API token)

**Request Body:**

```json theme={null}
{
  "bucketSid": "string (required)",
  "view": "request-log",
  "filters": {
    "environmentSid": "string",
    "criteria": "string",
    "streamViewSid": "string"
  },
  "ttl": 3600,
  "theme": "light"
}
```

**Response:**

```json theme={null}
{
  "token": "eyJhbGciOiJIUzI1NiJ9...",
  "expiresAt": "2025-03-17T05:27:00.000Z"
}
```

### Get Embed Requests

```
GET /v1/embed/requests?from={cursor}&limit={25}
```

**Authentication:** Bearer token (embed JWT)

### Get Embed Request Detail

```
GET /v1/embed/requests/{requestSid}
```

**Authentication:** Bearer token (embed JWT)

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Component shows 'No requests found'">
    Verify the `bucketSid` in your token request matches a bucket that has traffic. Check that any filters aren't too restrictive.
  </Accordion>

  <Accordion title="401 errors in the browser console">
    The embed token has likely expired. Implement the `apitraffic:token-expired` event handler (or `onTokenExpired` prop in React) to auto-refresh.
  </Accordion>

  <Accordion title="Styles look wrong or are overridden">
    The component uses Shadow DOM, so your page styles should not affect it. If you see issues, ensure you're setting CSS custom properties on the `apitraffic-request-log` element itself, not inside its shadow root.
  </Accordion>

  <Accordion title="Component doesn't render">
    Ensure the SDK script or import is loaded before the element appears in the DOM. With the CDN approach, place the `<script>` tag before the component. With bundlers, ensure `import '@apitraffic/embed'` is called.
  </Accordion>
</AccordionGroup>
