Embeddable Widgets
Two lines of HTML. Real Web Components, not an iframe.
Six widgets ship in widgets/loader.js: three free, key-metered widgets and three predictive widgets built on our own models. Every widget is a Shadow-DOM custom element, so it renders crawlable HTML and can't clash with your page's CSS. See them live at kickoffapi.com/widgets or build an embed interactively at the Widget Builder.
The embed — two lines
<script src="https://cdn.kickoffapi.com/widgets/loader.js" data-key="YOUR_KEY"></script>
<kickoff-standings league="lg_8K3mZ0pQ" season="2025"></kickoff-standings>
The <script> tag goes once per page and carries your key in data-key. Add as many widget elements as you like after it. A widget can also carry its own data-key attribute to override the page-level one.
The key requirement — read this before you embed
Mint a widget-scoped key from the panel's Widgets page (My API Keys → Widgets). Minting requires at least one allowed origin — a widget key cannot be created without one, because the origin restriction is what makes a stolen key harmless. Full account keys continue to work everywhere they always have; only use one in a widget embed if you accept that anyone who views your page source can use it.
Widgets — free
| Element | Required attributes | Optional attributes |
|---|---|---|
<kickoff-standings> Free |
league, season |
label, theme, demo |
<kickoff-livescores> Free |
league |
date (default: today), season, label, theme, demo |
<kickoff-fixtures> Free |
team or league (at least one) |
count (default: 6), season, label, theme, demo |
Note: the Widget Builder's field for kickoff-livescores is labelled "League id (optional)" — that is a builder UI copy bug. The loader itself refuses to render without league; this table matches the loader, which is the source of truth for what a widget actually needs.
Widgets — paid Predictive
From our own Dixon-Coles + xG models. Requires a plan with predictive access.
| Element | Required attributes | Optional attributes |
|---|---|---|
<kickoff-prediction> Paid |
fixture |
label, theme, demo |
<kickoff-seasonsim> Paid |
league, season |
label, theme, demo |
<kickoff-ratings> Paid |
league, season |
label, theme, demo |
A widget missing a required attribute renders "Couldn't load data (missing required attributes)" instead of calling the API.
On a locked plan
If your key can't reach predictive data, a paid widget does not render an error. It shows an upsell card — "Predictions are a paid feature" with a link to pricing — in place of the widget's normal content. This is what the API returns as 403 with code: "predictive_locked"; the loader recognises that specific response and swaps in the upsell card rather than failing.
Attributes common to every widget
| Attribute | Meaning |
|---|---|
theme="light|dark" | Defaults to light. Sets the widget's built-in color scheme. |
label="…" | Overrides the widget's header text — useful for localizing it. |
demo="true" | Renders sample data with no API call and no key required. Use it for previews, docs, or a page that hasn't been given a key yet. Leave the attribute off for live data — demo="false", demo="0" and demo="off" also mean live. |
data-key="…" | Overrides the page-level key from the loader's <script> tag for this one element. |
Theming
Every widget is a real Web Component with Shadow DOM, so it can't be styled by accident from your page's own CSS — theming happens through theme="light|dark" and four CSS custom properties:
| Property | Controls | Default (dark) | Default (light) |
|---|---|---|---|
--ko-accent | Highlight color — top-4 rank, live dot, primary bar segment | #FF3D2E | #FF3D2E |
--ko-bg | Card background | #0f1729 | #ffffff |
--ko-font | Card font family | System font stack (-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif) | |
--ko-radius | Card corner radius | 16px | |
Set them on the widget element itself:
kickoff-standings {
--ko-accent: #7c3aed;
--ko-font: "Inter", sans-serif;
--ko-radius: 4px;
}
Quota
Widget calls meter against your account's normal quota — a widget request is not a separate, unlimited allowance. If your embeds are heavily trafficked, watch your usage the same way you would for direct API calls; per-key usage already breaks out what each key (including a widget key) has spent.