For developers
The race embed
A race director copies one line from their dashboard and pastes it on their race's website. This page is for whoever builds that website: every option on the line, the iframe version, the browser events it sends and the public data it draws from.
6 min read
Directors set it up with a visual builder; the steps for them are in Embed on your website. Everything the builder does is an attribute on the line it writes, so you can edit the line by hand.
<script src="https://runthis.run/rt-race.js"
data-race="alder-quay-half"
data-layout="banner"
data-theme="dark"
async></script>The script draws in place of the tag, inside a shadow root, so your page's CSS can't break it and its CSS can't leak into your page. Put as many tags on a page as you like, for the same race or different ones; each draws its own. The element it adds is a div with the class runthis-race, so you can size or position it from your own stylesheet.
It fits the width of the element it sits in, not the screen, and redraws when that width changes. Give it a narrow column and it stacks; give it a wide one and it spreads out.
Only data-race is required. Anything misspelled or out of range falls back to its default, so a typo changes the look instead of breaking your page.
data-race(the race's slug): the part afterrunthis.run/event/in the race's address.data-layout(card, banner, compact, distances, hero, button, countdown, spots, price): the first five are whole cards; the last four are single pieces with no box, for placing each fact yourself. Defaultcard.data-show(a comma list of head, when, countdown, prices, increase, spots, button, powered): for whole cards, which parts to draw. Leave it off for all of them. Each part shows only when the race has something for it (no price increase coming, no increase line).data-detail(on or off): for single pieces, their one extra: the price on the button, the line under the countdown saying what it counts to, the registered count under spots left, or the whole price schedule under the price. On by default for the countdown only.data-box(on or off): its own background and border. On for whole cards, off for pieces. The hero always paints its photo.data-theme(light, dark, auto, site):autofollows the visitor's light or dark setting;siteuses your page's own text color. Defaultlight.data-accent(a hex color like #c2410c): the button and bar color. The button's words pick black or white on their own to stay readable.data-corners(rounded, pill, square): defaultrounded.data-font(runthis or inherit):inherituses your page's font instead of loading Run This's.data-button(up to 32 characters): the button's words. DefaultRegister.data-distance(a distance id): opens the registration page with that distance already picked. The ids are in the JSON below (subEvents[].id).data-countdown(race, price, close): what the countdown counts down to: race day, the next price increase or registration closing. Before registration opens, every countdown counts down to the opening.data-target(_self or _blank):_blankopens the registration page in a new tab.data-utm(letters, numbers, dots, dashes): a campaign name for the links, for a race with more than one site. Defaults to the race's slug.
For site builders that strip scripts, the same options work as query parameters on an iframe address, without the data- prefix:
<iframe src="https://runthis.run/embed/race/alder-quay-half?layout=countdown&theme=dark"
width="100%" height="110" style="border:0"
loading="lazy" title="Register for Alder Quay Half"></iframe>Links inside the frame open the registration page in the whole window, not in the frame. The frame also posts its height to your page every time it changes, so you can let it grow instead of guessing a height:
window.addEventListener("message", function (e) {
if (e.origin !== "https://runthis.run") return;
var m = e.data;
if (!m || m.rt !== "rt-race" || m.type !== "size") return;
var frame = document.querySelector('iframe[src*="/embed/race/' + m.slug + '"]');
if (frame) frame.style.height = m.height + "px";
});The embed sends two DOM events from its element. Both bubble, so you can listen on document.
Fired once it has drawn. detail is { slug, status, layout }, where status is the registration status from the JSON. Use it to hide a fallback link of your own once the embed is showing.
Fired when a visitor clicks one of its links. detail is { slug, kind, subEventId }, where kind is register, waitlist, view or powered, and subEventId is the distance's id when the link was for one distance (otherwise null). Send it to your own analytics if you like:
document.addEventListener("rt-race:click", function (e) {
console.log("Run This link clicked", e.detail.kind, e.detail.subEventId);
});Every link also carries utm_source=runthis&utm_medium=embed, and visits from it show as "Your website" in the race's own traffic sources.
Want to draw it yourself? The embed reads public JSON, and so can you:
GET https://runthis.run/api/rt-race/alder-quay-halfIt allows requests from any origin and is cached for a minute. A race that is a draft, registers somewhere else, or has been taken down answers 404. Private races work: private on Run This means unlisted, and the race's own website is a fine place for it. A shortened example:
{
"v": 1,
"slug": "alder-quay-half",
"name": "Alder Quay Half",
"url": "https://runthis.run/event/alder-quay-half",
"iconUrl": "https://example.com/icon.png",
"coverUrl": "https://example.com/cover.jpg",
"startsAt": "2027-04-17T12:00:00.000Z",
"timeZone": "America/Chicago",
"dateLabel": "Sat, Apr 17, 2027",
"placeLabel": "Riverside Park",
"lifecycle": null,
"registration": {
"status": "open",
"action": "register",
"label": "Register",
"note": "",
"opensAt": null,
"closesAt": "2027-04-15T04:59:00.000Z",
"url": "https://runthis.run/event/alder-quay-half/register"
},
"capacity": { "capacity": 500, "registered": 412, "left": 88,
"pct": 82, "full": false, "almostFull": false },
"fromPrice": { "cents": 4309, "display": "$43.09" },
"nextIncrease": {
"cents": 4840, "display": "$48.40",
"at": "2027-02-01T06:00:00.000Z", "daysAway": 3, "soon": true,
"label": "Goes up to $48.40 in 3 days",
"subEventId": "half", "subEventName": "Half Marathon",
"namedLabel": "Goes up to $48.40 in 3 days"
},
"countdown": {
"race": { "at": "2027-04-17T12:00:00.000Z",
"label": "until race day, Sat, Apr 17", "short": "days to go" },
"price": { "at": "2027-02-01T06:00:00.000Z",
"label": "until the price goes up to $48.40", "short": "days at this price" },
"close": { "at": "2027-04-15T04:59:00.000Z",
"label": "until registration closes Apr 14", "short": "days to register" },
"open": null
},
"subEvents": [
{
"id": "half",
"name": "Half Marathon",
"distance": "Half marathon",
"startLabel": "7:00 AM",
"price": { "cents": 4309, "display": "$43.09" },
"nextIncrease": { "cents": 4840, "display": "$48.40", "at": "2027-02-01T06:00:00.000Z",
"daysAway": 3, "soon": true, "label": "Goes up to $48.40 in 3 days" },
"schedule": [
{ "label": "Until Jan 31", "display": "$43.09", "current": true },
{ "label": "From Feb 1", "display": "$48.40", "current": false }
],
"spots": null,
"status": "open",
"statusLabel": "",
"registerUrl": "https://runthis.run/event/alder-quay-half/register?subEvent=half"
}
]
}- Prices are what checkout charges, fees included, in cents with a ready-made
displaystring.fromPriceis the lowest current price across the distances;nextIncreaseis the soonest increase, ornull. ItsnamedLabelnames the distance when the race has more than one. - registration.status is one of
open,waitlist,sold_out,not_open(withopensAt),opens_soon,closed,ended,cancelledorunavailable.actionsays what a button should do (register,waitlist,viewornone), andlabelis the race page's own button text. - capacity and each distance's spots are
nullunless the director shows capacity on the race page. A distance'sleftis never more than the race has left. - countdown holds each moment a clock can count to.
openis set only before registration opens. - lifecycle is set when the race is postponed or cancelled, with the race page's own wording.
- subEvents are the distances the race page lists, in its order. Distances with no price are left out, as on the page.
Fields may be added over time; nothing is renamed or removed without v changing.
- It sets no cookies and reads none of yours.
- It takes no payment and asks nobody to sign in on your page. The button opens the registration page on Run This.
- It shows nothing the race's public page doesn't already show.
- Apart from the race's own icon and cover photo, everything it loads comes from
runthis.run: the script, the JSON, the Run This logo and two fonts (none withdata-font="inherit").
Something missing?

