New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@apitraffic/embed

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@apitraffic/embed

Embeddable Web Components for displaying ApiTraffic request logs in any application.

latest
Source
npmnpm
Version
1.1.1
Version published
Weekly downloads
64
-84.35%
Maintainers
1
Weekly downloads
 
Created
Source




Website   •   Docs   •   Embed Documentation   •   X   •   Discord

@apitraffic/embed

npm license

@apitraffic/embed puts ApiTraffic inside your own product. It ships three Web Components:

  • <apitraffic-request-log>: a live, styled request log with expandable request detail, rendered in Shadow DOM so your page's CSS and the component's never collide. No iframe.
  • <apitraffic-report> and <apitraffic-dashboard> (from 1.1.0): one of your saved reports or dashboards, locked to one customer's slice of the traffic, drawn in an iframe of the ApiTraffic app.

Every embed is driven by a short-lived embed token that your server signs. The token, not the page, decides what the embed may show: the bucket, the filters, the request detail tabs and the features. A viewer editing the DOM cannot widen any of it.

The package also has a side-effect-free headless entry (the API client and token helpers, without any element) for rendering your own UI. React users: see @apitraffic/embed-react.

Contents

Requirements

  • A plan that includes Embeds. Report and dashboard embeds also need Custom reports. If your plan stops including them, new embed tokens are refused and embeds already on your pages stop loading.
  • An Administrator API token with no access scopes on your server, to mint embed tokens. The ingest token your SDK sends traffic with, and User tokens, are refused (403, code 2010). Only account owners and admins can create one.
  • A server endpoint of your own that mints a token for the signed-in user. The browser never sees your API token.
  • A browser with Custom Elements v1, Shadow DOM and ES2020 (see Browser support).
  • Network access from the viewer's browser to https://api.apitraffic.io (request log) or https://app.apitraffic.io (reports and dashboards).
  • Node.js 20 or newer for tooling that imports the package (bundlers, SSR, tests). The package has no runtime dependencies.

Installation

npm install @apitraffic/embed
pnpm add @apitraffic/embed
yarn add @apitraffic/embed

CDN (no build step)

The package's unpkg and jsdelivr fields point at a minified IIFE build for <script> tags. It registers the elements and exposes the exports on a global ApiTrafficEmbed.

<script
  src="https://unpkg.com/@apitraffic/embed@1.1.0/dist/apitraffic-embed.iife.js"
  integrity="sha384-<base64 SHA-384 of that exact file>"
  crossorigin="anonymous"></script>

Pin an exact version (@1.1.0, never @latest or no version) and add an integrity hash, so the script that runs on your page is the one you reviewed, and a changed or compromised CDN file is refused by the browser instead of executed. Compute the hash of the file you pin:

curl -fsSL https://unpkg.com/@apitraffic/embed@1.1.0/dist/apitraffic-embed.iife.js \
  | openssl dgst -sha384 -binary | openssl base64 -A

Update the version and the hash together, and replace the placeholder before shipping: depending on the browser, a placeholder hash makes it refuse the script (Firefox) or ignore the check (Chrome).

Package entry points

The package ships ESM, CommonJS and TypeScript declarations, resolved through its exports map. Bundlers pick the ESM build; the IIFE is only for <script> tags.

ImportBuildUse it for
import '@apitraffic/embed'ESM (dist/apitraffic-embed.esm.js)Bundlers (Vite, webpack, esbuild), native ESM. Registers the elements.
require('@apitraffic/embed')CJS (dist/apitraffic-embed.cjs)CommonJS, Jest, SSR. Registers the elements in a browser.
@apitraffic/embed/headlessESM / CJS (dist/headless.*)The API client and token helpers only. Registers nothing.
dist/apitraffic-embed.iife.jsIIFE, minifiedCDN / <script> tag (unpkg, jsdelivr).
dist/index.d.ts, dist/index.d.cts, dist/headless.d.ts, dist/headless.d.ctsTypeScriptTypes for import and require.

Files under dist/ stay importable by path (@apitraffic/embed/dist/...). Tools that ignore the exports map (TypeScript moduleResolution: "node", Jest before 28, webpack 4) still resolve @apitraffic/embed/headless, through typesVersions and a headless/package.json that points into dist/.

Quick start

1. Mint an embed token on your server

If you already run an ApiTraffic Node SDK, embedToken() does it in one call. It is exported by @apitraffic/node, @apitraffic/express, @apitraffic/fastify, @apitraffic/koa and @apitraffic/hapi (and the framework packages built on @apitraffic/node). Set an Administrator API token with no access scopes as API_TRAFFIC_EMBED_TOKEN; the bucket, account and host come from the SDK you already configured.

const apitraffic = require('@apitraffic/node');

// Behind YOUR app's sign-in (requireUser is your auth middleware): an embed
// token is a read credential, so never mint one for an anonymous caller, and
// never take filters, tabs, features or ttl from the request.
app.get('/api/embed-token', requireUser, async (req, res) => {
  try {
    const { token, expiresAt } = await apitraffic.embedToken({
      filters: {
        // Written into the criteria as tag:customerId == "…", with the value
        // escaped, so an id containing a quote cannot add a condition.
        tags: { customerId: req.user.customerId }
      },
      ttl: 900,                                   // seconds, 60–86400
      tabs: ['request', 'response', 'connection', 'tags'],
      features: { copyRequest: false, includeBodies: false }
    });
    res.json({ token, expiresAt });
  } catch (error) {
    // embedToken() rejects rather than going quiet: a failure is an error on
    // your route, not a blank embed.
    console.error(error); // the message names your server's configuration: keep it out of the response
    res.status(502).json({ error: 'Could not create an embed token' });
  }
});

Not on Node, or in a service that does not run the SDK? Call the API directly. It is the same endpoint embedToken() uses:

const response = await fetch(`https://api.apitraffic.io/v1/accounts/${ACCOUNT_SID}/embeds/token`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.API_TRAFFIC_EMBED_TOKEN}`, // Administrator API token, no access scopes
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    bucketSid: 'your-bucket-sid',
    filters: { criteria: 'method == "GET"' },
    ttl: 900,
    tabs: ['request', 'response', 'connection', 'tags'],
    features: { copyRequest: false, includeBodies: false }
  })
});
if (!response.ok) throw new Error(`ApiTraffic refused the embed token (${response.status})`);
const { token, expiresAt, tabs, features } = await response.json();

See Minting embed tokens for every parameter, and the Node SDK's embed token docs for what embedToken() fills in.

2. Render the element

Replace the integrity placeholder with the real hash of the file, computed as shown in CDN (no build step), or the browser refuses to run the script.

<script
  src="https://unpkg.com/@apitraffic/embed@1.1.0/dist/apitraffic-embed.iife.js"
  integrity="sha384-<base64 SHA-384 of that exact file>"
  crossorigin="anonymous"></script>

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

<script>
  const el = document.getElementById('log');

  async function loadToken() {
    const res = await fetch('/api/embed-token');
    if (!res.ok) throw new Error(`token route answered ${res.status}`);
    const { token } = await res.json();
    // A property, not an attribute: the component keeps the token privately
    // and it never appears in the DOM.
    el.token = token;
  }

  const reload = () => loadToken().catch((error) => console.error('embed token:', error));
  reload();
  // About 30 s before the token expires, when it has, or when the API rejects it.
  el.addEventListener('apitraffic:token-expired', reload);
</script>

Or with a bundler:

import '@apitraffic/embed'; // registers <apitraffic-request-log>, <apitraffic-report>, <apitraffic-dashboard>

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

That is the whole integration. The component loads the first page as soon as it is on the page and has a token.

Minting embed tokens (server-side)

[!WARNING] The token that mints embed tokens is a read credential to your captured traffic. An Administrator API token can mint embed tokens for any bucket in its account, with any filters or none, so whoever holds it can read all of the account's captured traffic through an embed. Keep it in a secret store (never in browser code, container images or a repository), and rotate it immediately if it may have been exposed. Deleting it stops it minting embed tokens; embed tokens it already minted stay valid until they expire (ttl, at most 24 hours).

Protect the route that mints tokens. An embed token is a read credential for the traffic it is scoped to, so the endpoint on your server that creates one is as sensitive as that data:

  • Require your user's sign-in on it. Never mint a token for an anonymous caller.
  • Decide filters, tabs, features and ttl on the server, from the signed-in user. Never copy them from the request body or query string.
  • Keep ttl short (minutes, not hours). The component asks for a fresh token before expiry.

The sample app's server.js (in examples/sample-app in the ApiTraffic source, not the npm package) shows all three.

Request log token parameters

POST https://api.apitraffic.io/v1/accounts/{accountSid}/embeds/token, authenticated with an Administrator API token with no access scopes.

ParameterTypeDefaultDescription
bucketSidstring-Required on the HTTP endpoint. embedToken() defaults it to the bucket the SDK reports to.
viewstring'request-log''request-log', 'report' or 'dashboard'. See Reports and dashboards for the other two.
filters.environmentSidstring-Only this environment.
filters.criteriastring-Search criteria, same syntax as the request log (method == "GET"). Conditions are joined with AND; OR between conditions is refused, and so is a value with an unescaped quote (inside a double-quoted value, escape " as \" and \ as \\). With the Node SDK, use filters.tags or escapeCriteriaValue().
filters.streamViewSidstring-Use a saved stream view's filter.
ttlnumber3600Token lifetime in seconds, 60 to 86400.
themestring'light''light', 'dark' or 'auto'. Returned to the embed as config.theme. The request log element itself is themed by its theme attribute.
tabsstring[]default setThe request detail tabs this embed may show. See Tabs. [] hides the request detail entirely.
features.copyRequestbooleanfalseSee Features.
features.includeBodiesbooleanfalseSee Features.

The response is { token, expiresAt, tabs, features }. tabs and features are the resolved values: order normalized, defaults filled in. An unknown tab name is refused with 400, not dropped. Store or log them if you need a record of what a given embed was allowed to show.

<apitraffic-request-log> reference

Attributes

AttributeTypeDefaultDescription
tokenstring-Required. The signed embed JWT. Set the token property instead (below); the attribute is read for compatibility, but anything in an attribute is readable by every script on the page and recorded by session-replay tools.
themestring'light''light', 'dark' or 'auto' (follows the OS via prefers-color-scheme).
api-urlstring'https://api.apitraffic.io/v1'The API base URL (self-hosted or staging).
page-sizenumber25Requests per page. The API accepts 1 to 100.
show-detailstring'true''false' disables expanding a row into its detail.
noncestring-Copied onto the fallback <style> element in browsers without constructable stylesheets. See Content Security Policy.

All attributes except nonce are reactive (nonce is read once, when the element is connected, so set it before inserting the element). Changing api-url or page-size reloads the list (once, however many change together); changing theme re-renders nothing, since the stylesheet keys off the attribute; turning show-detail off collapses an open row.

There is deliberately no attribute for tabs or features: the token decides, so a viewer editing the DOM cannot reveal one.

Properties

PropertyTypeDescription
tokenstring | nullThe embed JWT. Held privately in the component and never written to an attribute. Setting a new token clears the previous token's rows and loads afresh; el.token = null clears the log.
themestringReflects theme.
apiUrlstringReflects api-url.
pageSizenumberReflects page-size.
showDetailbooleanReflects show-detail.
tabsstring[] (read-only)The tabs the token exposes, once data has loaded.
featuresobject (read-only)The features the token enables, once data has loaded.

Properties set before the element is defined (a framework that renders first and loads this package later) are picked up when it is.

Events

All events are CustomEvents that bubble and cross the shadow boundary (composed: true).

EventdetailWhen
apitraffic:ready{ tabs, features }The first page loaded: on first load, and again after a new token, api-url or page-size reloads the list.
apitraffic:error{ code, message }An API call failed (code is the API's error code, when it sent one), the page handed back an expired token (TOKEN_EXPIRED), or Copy request could not use the clipboard (CLIPBOARD_UNAVAILABLE, CLIPBOARD_FAILED).
apitraffic:token-expired{ reason }'expiring' about 30 s before the token's exp; 'expired' once it has passed and the page has not replaced it; 'unauthorized' when the API answered 401. Set a new token.
apitraffic:request-click{ requestSid }A row was expanded. Not fired when a row is collapsed, or when show-detail="false".
const el = document.querySelector('apitraffic-request-log');

el.addEventListener('apitraffic:ready', (e) => {
  console.log('Tabs:', e.detail.tabs);         // e.g. ['request', 'response', 'tags']
  console.log('Features:', e.detail.features); // { copyRequest: false, includeBodies: false }
});
el.addEventListener('apitraffic:error', (e) => console.warn(e.detail.code, e.detail.message));
el.addEventListener('apitraffic:request-click', (e) => console.log('Opened', e.detail.requestSid));

Token refresh

Listen for apitraffic:token-expired, fetch a new token from your backend and set it on the element. The component re-fetches by itself.

The component asks; it never loops:

  • Clock skew. A browser cannot read the server's clock, so the component corrects for the visitor's clock only when the token itself proves it wrong, using the token's iat (the server's time when it was minted). A clock that is behind by more than a few seconds (iat in the future) is corrected for, so the token is not held past its real expiry. A clock that is ahead by more than the time the token has left makes it look expired on arrival; that token is read as fresh as of its iat instead of triggering an immediate, repeating "expired". A clock ahead by less than that is not corrected: the token is treated as expiring that much early, so apitraffic:token-expired fires sooner (once, with the usual backoff), never in a loop.
  • The API decides. A 401 from the API is treated as the token's real end, whatever the clock says.
  • An expired token handed back. If the page answers apitraffic:token-expired with a token that has already expired (the same one, or another just as old), the log shows "Token refresh returned an expired token." and fires apitraffic:error with code: 'TOKEN_EXPIRED', instead of an empty list.
  • Backoff. While no load succeeds, each further apitraffic:token-expired waits twice as long as the last (1 s, 2 s, 4 s … up to 5 minutes).

Tabs

Clicking a row expands the same request detail you see in the ApiTraffic app. The token's tabs decide which of these the embed shows. A tab that is not on the token is not rendered and its data is not returned by the API: nothing is merely hidden client-side.

TabShows
requestRequest line, headers and query string.
responseStatus, headers and content type.
connectionPer-phase connection timings.
wafWhat the application firewall did with the request.
relatedOther requests sharing this request's context.
workflowsWorkflow executions the request triggered.
tagsThe request's tags.
waterfallAPM spans, drawn as a waterfall.
tracesTrace identifiers carried on the request.
traceEvery request sharing this request's distributed trace.
concurrencyWhat else the request's server was doing at the time.
aiA fix prompt for the failure, with correlated deploys. Shown only on failed (4xx/5xx) requests.

Default (when tabs is omitted): request, response, connection, workflows, tags, traces, ai, the tabs that describe the request itself. tabs: [] leaves a read-only list of rows.

Some tabs only appear when they apply: connection needs per-phase timings, ai is offered only on 4xx/5xx, and captures (requests with no response) show only request.

Four tabs answer with data from beyond the request being viewed. Think about each before granting it to an audience that is not your own team:

  • related and trace follow the request into other traffic in the same bucket, including requests the token's criteria would otherwise exclude.
  • concurrency lists requests that ran on the same server, which can include traffic from your other buckets.
  • ai (in the default set) includes the deploys correlated with the failure: repository names, branches and commit messages from your git integration.

Features

Behaviours that are not tabs, passed as features on the token:

FeatureDefaultDescription
copyRequestfalseAdds a Copy request button that generates the request as code: cURL, JavaScript (fetch and axios), Python, PHP, Go, Ruby, Java and C#. Exposes the request headers and query string even when the request tab is not shown. Captured values are never spliced into code: every value goes through its language's literal syntax.
includeBodiesfalseReturns request and response bodies for the request and response tabs.

Bodies are withheld even on the request and response tabs unless includeBodies is set: they are the likeliest place for something an embed's audience should not see. Your account's redaction rules still apply, so bodies reach the embed already redacted. When shown, a JSON body renders as a collapsible tree (the first two levels open) with a Raw toggle for selecting and copying the text; other bodies render as text.

Theming

theme="light" (the default), theme="dark", or theme="auto" to follow the viewer's prefers-color-scheme. Beyond that, the component's Shadow DOM reads CSS custom properties that you set on the element (or any ancestor):

These values apply whatever the theme. To keep theme="dark" and theme="auto" readable, scope colour overrides to the light theme, as below (the default, so match "not dark or auto") or set dark values too; fonts, radius and height are safe to set unconditionally.

apitraffic-request-log:not([theme="dark"]):not([theme="auto"]) {
  --at-font-family: 'Inter', sans-serif;
  --at-font-size: 13px;
  --at-bg-color: #ffffff;
  --at-text-color: #1f2937;
  --at-text-muted: #6b7280;
  --at-accent-color: #3b82f6;
  --at-danger-color: #ef4444;
  --at-border-color: #e5e7eb;
  --at-border-radius: 6px;
  --at-row-hover-bg: #f9fafb;
  --at-max-height: 600px; /* constrain the height and scroll */
}
PropertyDefaultUsed for
--at-font-familySystem font stackFont family
--at-font-size13pxBase font size
--at-bg-color#ffffffContainer background
--at-text-color#1f2937Primary text
--at-text-muted#6b7280Secondary text
--at-accent-color#3b82f6Links, buttons, accents
--at-danger-color#ef4444Errors
--at-border-color#e5e7ebBorders
--at-border-radius6pxContainer corner radius
--at-row-hover-bg#f9fafbRow hover background
--at-detail-row-borderrgba(0,0,0,0.04)Dividers inside the request detail
--at-focus-color--at-accent-colorKeyboard focus rings
--at-max-heightnoneMaximum height; set it to make the log scroll

The dark theme (dark, or auto on a dark OS) supplies its own values for --at-bg-color, --at-text-color, --at-text-muted, --at-border-color and --at-row-hover-bg. Values you set on the element take precedence over them, with one exception: in dark mode the outer container's background and border are fixed at #111827 and #374151.

Set custom properties on the apitraffic-request-log element itself, not inside its shadow root: your page's other styles do not reach into the component.

The report and dashboard embeds are drawn by the ApiTraffic app inside their iframe, and themed by the token's theme, not by these properties.

Reports and dashboards

<apitraffic-report> and <apitraffic-dashboard> (from 1.1.0) show one of your saved reports or dashboards to your own customers: "your API calls this month", "your error rate". Your server signs a filter, typically the customer's id, into the token; the embed ANDs it onto the report's own filter on ApiTraffic's side every time. The viewer can pick a time range and nothing else.

1. Allow embedding

In the ApiTraffic app, open the report or dashboard (it must be shared with Everyone in the account), click Embed and turn on Allow embedding (owners and admins; anyone who can edit it can turn it off). The dialog shows its ID (Report ID or Dashboard ID) and a preview. Embed Setup (account settings, Integration → Embed) lists everything that allows embedding. Some reports cannot be embedded, for example one grouped by IP address, by firewall policy or rule, or by a catalogue attribute of a Personal tag; the dialog gives the reason. See Embedded Reports and Dashboards for the full rules.

2. Mint a token

// Server-side only. Take the customer from the signed-in user, never from the request.
const { token } = await apitraffic.embedToken({
  view: 'dashboard',                       // or 'report' with reportSid
  dashboardSid: 'dash_...',
  filters: { tags: { customerId: req.user.customerId } },
  timeRanges: ['last_24h', 'last_7d', 'last_30d'],
  timeRange: { relative: 'last_30d' },
  ttl: 900
});

Over HTTP, the same endpoint takes the filter as a string: "filters": { "filter": "tag:customerId == \"org_8f2a\"" }.

ParameterDescription
view'report' or 'dashboard'.
reportSid / dashboardSidWhat the embed shows. It must allow embedding.
filters.filterA filter in the report filter language, ANDed with the report's (and dashboard's) own, at most 4000 characters. With the Node SDK, filters.tags builds and escapes it for you. A tag classified Secret is refused.
filters.environmentSidOnly count this environment.
bucketSidOnly count this bucket. Without it the report keeps its own scope.
timeRangesThe ranges the viewer may pick: last_1h, last_24h, last_7d, last_30d, last_90d, today, yesterday, this_month, previous_month. Default: all of them.
timeRange{ relative }: the range the embed opens with, one of timeRanges.
ttlLifetime in seconds, 60 to 86400 (default 3600).
theme'light', 'dark' or 'auto'.

The request log's criteria, streamViewSid, tabs and features do not apply to reports and are refused. A token works only with its own view (403 EMBED_VIEW_MISMATCH otherwise).

3. Place the element

<apitraffic-dashboard id="usage" frame-title="Your API usage"></apitraffic-dashboard>

<script>
  const el = document.getElementById('usage');
  const refresh = () => fetch('/api/usage-embed-token')
    .then((r) => { if (!r.ok) throw new Error(`token route answered ${r.status}`); return r.json(); })
    .then(({ token }) => { el.token = token; })
    .catch((error) => console.warn('Could not refresh the embed token', error));

  refresh();
  el.addEventListener('apitraffic:token-expired', refresh);
  el.addEventListener('apitraffic:error', (e) => console.warn(e.detail.code, e.detail.message));
</script>

Use <apitraffic-report> for a single report. Load version 1.1.0 or later.

Unlike the request log, these elements wrap an iframe of the ApiTraffic app's /embed page, so charts look exactly as they do in the app, your page's styles never reach them, and the frame never reads your page. The element hands the frame its token by postMessage (to the app's origin only; never in a URL), sizes the frame to its content, and reads messages only from its own frame and the app's origin.

AttributeDefaultDescription
token-The embed JWT. Prefer the token property, which keeps it out of the DOM. Setting a new one hands it to the frame; el.token = null clears the embed.
app-urlhttps://app.apitraffic.ioThe ApiTraffic app the frame loads from (self-hosted or staging). Only its origin and path are used: the frame loads <origin><path>/embed, and a query string or fragment is dropped. Changing it loads a new frame. A value that is not an http(s) URL is an apitraffic:error (INVALID_APP_URL) until a valid one is set.
frame-titleApiTraffic report / ApiTraffic dashboardThe frame's accessible name: say what it shows. A title set on the element is moved here (and removed from the element), so the name is not announced twice.
PropertyDescription
tokenAs above.
appUrlReflects app-url.
appOrigin (read-only)The origin the frame is loaded from, and the only one its messages are accepted from.
embedSrc (read-only)The frame's address, or null for an app-url that is not http(s).

The theme, the locked filter and the time ranges offered are all in the token, so there is no attribute for them.

Events follow the request log's token contract (all bubble and are composed):

EventdetailWhen
apitraffic:ready{}The report or dashboard loaded.
apitraffic:error{ code, message }Something went wrong, for example EMBED_NOT_AVAILABLE (embedding turned off, or the item was deleted or made private), INVALID_EMBED_TOKEN, EMBED_VIEW_MISMATCH, FEATURE_NOT_IN_PLAN, INVALID_REPORT (the report could not run; the viewer sees a plain message), RATE_LIMITED, INVALID_APP_URL, or TOKEN_EXPIRED (the page handed back the same token after apitraffic:token-expired, and it has expired).
apitraffic:token-expired{ reason }'expiring' about 30 s before the token's exp (read against the server's clock, from the token's iat), then 'expired' at exp if the token was not replaced, or whenever the API refused it as expired. Repeated notices without a successful load back off (1 s, 2 s, 4 s … up to 5 minutes).

Pages with a Content Security Policy need frame-src https://app.apitraffic.io (or your app-url's origin). The host page must be served from an http(s) origin: a page opened from file: or an app-specific scheme (app://, capacitor://, …) cannot frame the embed.

Headless usage and the programmatic API

Render your own UI with @apitraffic/embed/headless: the API client and the token helpers, without any Web Component. Importing it registers no custom element and touches no DOM API, so it is safe anywhere, including a server render. (@apitraffic/embed-react's useApiTrafficEmbed hook is built on it.)

import { EmbedApi, clockOffsetFor, secondsUntilExpiry } from '@apitraffic/embed/headless';

const api = new EmbedApi('https://api.apitraffic.io/v1', token);
const controller = new AbortController();

const page = await api.getRequests({ limit: 25, signal: controller.signal });
// page: { records, hasMore, from, config: { view, theme, tabs, features } }

if (page.hasMore) {
  const next = await api.getRequests({ from: page.from, limit: 25 });
}

// Remaining lifetime, read against the server's clock.
const offset = clockOffsetFor(token);
console.log(secondsUntilExpiry(token, offset)); // e.g. 3540

Records only carry the fields the token's tabs allow, so check config.tabs before rendering anything beyond the row itself.

The main entry (@apitraffic/embed) exports everything the headless entry does, plus the elements and their tag names.

Exports

ExportEntryDescription
EmbedApibothThe embed API client (below).
decodeToken(token)bothThe JWT's payload, without verifying it, or null when it is not a JWT.
isTokenExpired(token, bufferSeconds = 30, clockOffsetSeconds = 0)bothWhether the token has expired, or expires within bufferSeconds. A token without exp counts as expired.
secondsUntilExpiry(token, clockOffsetSeconds = 0)bothSeconds until exp, never negative.
clockOffsetFor(token, bufferSeconds = 30, nowSeconds?)bothServer time minus local time, in seconds, derived from the token's iat. 0 unless the local clock is provably off (see Token refresh).
RequestLogElementmainThe <apitraffic-request-log> class.
EmbedFrameElementmainThe base class of the report and dashboard elements.
ReportEmbedElement, DashboardEmbedElementmainThe <apitraffic-report> and <apitraffic-dashboard> classes.
TAG_NAME, REPORT_TAG_NAME, DASHBOARD_TAG_NAMEmain'apitraffic-request-log', 'apitraffic-report', 'apitraffic-dashboard'.

The token helpers only read the token. The signature is checked by the API on every request, and a 401 from it is the authoritative "this token is no longer valid".

EmbedApi

new EmbedApi(apiUrl?, token?). apiUrl defaults to https://api.apitraffic.io/v1. Every method takes an optional signal (an AbortSignal) so you can cancel a request you no longer want, because the token changed or the view left the page.

MethodEndpointNotes
setToken(token)-Use a new token for later calls.
getRequests({ from?, limit?, signal? })GET /embed/requestsA page of the log. from is the previous page's cursor; limit defaults to 25 (the API accepts 1 to 100).
getRequest(requestSid, { signal? })GET /embed/requests/{sid}One request, filtered to the granted tabs.
getRelated(requestSid, { signal? })GET /embed/requests/{sid}/relatedNeeds the related tab.
getTrace(requestSid, { signal? })GET /embed/requests/{sid}/traceNeeds the trace tab. traceId is null when the request is not part of one.
getConcurrent(requestSid, { signal? })GET /embed/requests/{sid}/concurrentNeeds the concurrency tab.
getFixPrompt(requestSid, { lookbackMinutes?, signal? })GET /embed/requests/{sid}/fix-promptNeeds the ai tab. lookbackMinutes sets how far back to correlate deploys.

A non-2xx answer rejects with an Error carrying status (the HTTP status; 401 means the token is expired or invalid) and code (the API's error code, when it sent one). An endpoint whose tab is not granted answers as though the request does not exist. A cancelled call rejects with an AbortError.

TypeScript

Declarations ship with the package, for both import and require, and for both entries. No @types package is needed.

import type { RequestLogElement, RequestLogTokenExpiredDetail, EmbedRequestsPage } from '@apitraffic/embed';

const el = document.querySelector('apitraffic-request-log'); // typed as RequestLogElement | null
el?.addEventListener('apitraffic:token-expired', (e) => {
  console.log(e.detail.reason); // 'expiring' | 'expired' | 'unauthorized' | undefined
});

Importing the main entry adds the three tags to HTMLElementTagNameMap, and the elements' addEventListener overloads type each apitraffic:* event's detail.

TypeEntryDescribes
EmbedTabbothA tab name a token can grant.
EmbedFeaturesboth{ copyRequest?, includeBodies? }.
EmbedConfigboth{ view, theme, tabs, features }, as resolved by the API.
EmbedRequestRecordbothOne captured request; every field beyond sid is optional.
EmbedRequestsPageboth{ records, hasMore, from, config? }.
EmbedRequestDetailbothOne request, with config.
EmbedApiErrorbothThe error EmbedApi rejects with (status, code).
EmbedRequestOptions, GetRequestsOptions, GetFixPromptOptionsbothMethod options.
EmbedTokenPayloadbothThe decoded payload (exp, iat, other claims).
RequestLogReadyDetail, RequestLogErrorDetail, RequestLogTokenExpiredDetail, RequestLogRequestClickDetailmaindetail of the request log's events.
RequestLogElementEventMapmainEvent name to event type, for the request log.
EmbedFrameErrorDetail, EmbedFrameTokenExpiredDetail, EmbedFrameElementEventMapmainThe same, for the report and dashboard elements.

Server-side rendering

Importing @apitraffic/embed on a server (Next.js, Remix, Nuxt, plain Node) is safe: outside a browser it registers nothing and touches no global, and the elements are registered when the same module loads in the browser. Nothing is fetched until an element is on a page and has a token. @apitraffic/embed/headless has no side effects at all.

Security model

  • Signed server-side. The embed JWT is signed with a per-account HMAC-SHA256 secret. Any tampering invalidates the signature.
  • Filters are locked into the token. Bucket, environment, criteria and stream view (or a report's filter) come from the verified token; query parameters from the browser are ignored for filtering, and only the pagination cursor (from, limit) is accepted.
  • Tabs are the access boundary. Exposed tabs and features are signed into the token and enforced server-side: the API rebuilds every response from the fields the granted tabs need, so a tab you do not grant contributes no data to the payload at all. The per-tab endpoints (related, trace, concurrent, fix-prompt) answer as if they do not exist unless their tab is granted, so a viewer cannot probe for what was withheld.
  • Every read is re-scoped. A single request is re-checked against the token's bucket and environment, so a guessed request sid outside the embed's scope reads as not found.
  • Short-lived. Tokens last ttl seconds (default one hour, at most 24 hours), with automatic refresh support. A stolen token shows only what the embed was designed to show, and only until it expires.
  • Kept out of the DOM. The elements hold the token privately and never reflect it into an attribute, so session-replay tools and other scripts reading the page do not see it. Report and dashboard elements pass it to their frame by postMessage, to the app's origin only.
  • Embed endpoints accept only embed JWTs. Standard API tokens and session tokens are rejected by /v1/embed/*, and an embed JWT cannot call any other ApiTraffic endpoint.
  • Your minting route is your responsibility. It must require your user's sign-in and fix the scope server-side. See Minting embed tokens.

Content Security Policy

The request log works under a strict policy with no 'unsafe-inline':

  • Its styles are a constructed stylesheet adopted by its shadow root (shared by every instance on the page), not a <style> element, and geometry such as waterfall bars is set through the CSSOM rather than style="..." attributes.
  • It needs connect-src https://api.apitraffic.io (or your api-url's origin), plus script-src for wherever the script comes from (https://unpkg.com for the CDN build).
  • Browsers without constructable stylesheets (Safari before 16.4) fall back to a <style> element, which a strict policy only allows with a nonce: set nonce="..." on <apitraffic-request-log> and the component copies it.
  • Trusted Types are not supported. The component renders its shadow DOM by assigning HTML strings to innerHTML (every API value is escaped first), so a page that enforces require-trusted-types-for 'script' will block its rendering. Leave Trusted Types unenforced on pages that host the component, or use the headless entry (@apitraffic/embed/headless, or useApiTrafficEmbed in React) and render the data yourself.

Report and dashboard embeds need frame-src https://app.apitraffic.io (or your app-url's origin).

Accessibility

Rows are keyboard-operable buttons (Enter / Space) that report aria-expanded; the detail tabs are a WAI-ARIA tablist (arrow keys, Home, End); loading, results and errors are announced through a polite live region; and focus rings are kept for keyboard users (--at-focus-color recolors them). Report and dashboard frames take their accessible name from frame-title.

Browser support

Any browser with Custom Elements v1, Shadow DOM and ES2020:

  • Chrome 80+
  • Edge 80+
  • Firefox 74+
  • Safari 13.1+

Browsers without constructable stylesheets (Safari before 16.4) use the <style> fallback described under Content Security Policy. Copy request needs the asynchronous Clipboard API (navigator.clipboard.writeText); where it is missing or refused, the component fires apitraffic:error with CLIPBOARD_UNAVAILABLE or CLIPBOARD_FAILED.

Troubleshooting

The token request is refused with 403. Only Administrator API tokens can mint embed tokens; ingest tokens (the one your SDK sends traffic with) and User tokens get 403 with code 2010. Create an Administrator token with no access scopes under Account → API Tokens and set it as API_TRAFFIC_EMBED_TOKEN (a scoped Administrator token gets the same 403, with a message saying its access scopes do not allow the action). A 403 with code 2221 means your plan does not include Embeds.

"No requests found". Check that the token's bucketSid has traffic, and that its filters are not too narrow.

A tab I expected is missing. Check in order: is it on the token (read element.tabs, or e.detail.tabs from apitraffic:ready; the default set excludes waf, related, trace, concurrency and waterfall)? Does it apply to this request (connection needs phase timings, ai only appears on 4xx/5xx, captures show only request)? A granted tab with nothing to show says so rather than disappearing.

Bodies are empty on the Request or Response tab. Set features.includeBodies: true on the token. If they are still empty, check your account's redaction rules and that body capture is on for the bucket.

401 errors in the browser console. The token expired or was rejected. The element fires apitraffic:token-expired with reason: 'unauthorized'; handle it (or onTokenExpired in React). If the 401s continue, your endpoint is probably serving a cached token rather than minting a new one.

"Token refresh returned an expired token." Your apitraffic:token-expired handler set a token that has already expired: usually a cached one. Mint a new token on every call.

Styles are blocked by your Content Security Policy. Current browsers need no style-src allowance. Safari before 16.4 falls back to a <style> element: set nonce="..." on the element.

Styles look wrong. Set CSS custom properties on the apitraffic-request-log element itself. Your page's other styles do not reach into the shadow root, by design.

The element never renders. Make sure the package is loaded: import '@apitraffic/embed' in a bundler, or the <script> tag on the page (check the browser console for an integrity mismatch, which blocks the script). The element upgrades in place whenever the script loads.

A report or dashboard embed stays on "Waiting for access…". No token reached it. Check your token route, and that the token is set on the element (el.token = …).

A report or dashboard says "This report is no longer available." Embedding was turned off, or the item was made private or deleted (apitraffic:error with EMBED_NOT_AVAILABLE).

More in the docs: request log troubleshooting and report and dashboard troubleshooting.

Sample application

The ApiTraffic source (not the npm package) includes examples/sample-app, an Express server that mints embed tokens behind a sign-in, with a page that renders <apitraffic-request-log> under a strict CSP and live controls for theme, page size, detail rows and height, plus an event log.

# from libs/client-libraries/apitraffic-embed
npm install
npm run build

cd examples/sample-app
cp .env.example .env   # set APITRAFFIC_API_TOKEN (Administrator), APITRAFFIC_ACCOUNT_SID, APITRAFFIC_BUCKET_SID
npm install
npm start              # http://localhost:3456, sign in with the user and password printed in the terminal

Its README lists every setting.

Changelog

Release history is on npm.

License

MIT

Community

Follow us on our Blog and on X.

Chat with the ApiTraffic team and other members on Discord and follow our tutorials and other videos on YouTube.

ApiTraffic Discord

ApiTraffic YouTube

ApiTraffic on X

Keywords

apitraffic

FAQs

Package last updated on 25 Oct 2026

Related posts