Relay
DocsSign inStart free

Install it

Two script tags and a key. The snippets below are addressed at this deployment, so they work as pasted.

1. Get a key

Create a workspace, then Install mints an ingest key. It is shown once; what is stored is a sha256 and a readable prefix.

The key is public by design — it sits in your page, like a Sentry DSN — so it is scoped until that is survivable: it can file a report into one workspace and nothing else. It cannot read a report, list one, see a screenshot or change a setting, because none of those accept a key at all. Fill in the origins your product is served from and a lifted key cannot be used from another website.

2. Paste two tags

<!-- The flight recorder. Blocking, and as early as your template allows. -->
<script src="https://olomon.com/relay-client/recorder.js"
        data-relay-surface="your-app"
        data-relay-endpoint="https://olomon.com/api/relay/report"
        data-relay-key="rlk_…"
        data-relay-build="${YOUR_COMMIT_SHA}"></script>

<!-- The camera and the panel. Deferred; nothing needs it until a press. -->
<script type="module" src="https://olomon.com/relay-client/relay.js"></script>

The first tag blocks on purpose. A deferred recorder installs after your own bundles and misses the requests that were already in flight — which is the most common way a feature like this ends up collecting nothing useful. It is one small file.

data-relay-build is the single highest-value attribute here. Without it, which commit the person was running is a guess, and a fix verified against the wrong one looks like it did not work.

3. Teach it about your product

The extractors are guessing; you are not. One adapter that returns whatever actually matters is the highest-value thing you can add, and it is ranked above every inference in the brief.

window.__olomonRelayRecorder.adapter({
  name: "Batch state",
  match: () => location.pathname.startsWith("/payments"),
  extract: () => ({
    batchId: store.batch.id,
    rejected: store.batch.rejected,
    locked: store.batch.locked,
  }),
});

What happens with no endpoint

Leave data-relay-endpoint off and the button still works: it renders the brief in the browser and offers Copy and Download. That is also what happens automatically when the relay cannot be reached, so a report is never lost to a network error.

Things that are easy to get wrong

Serving the client from your own origin
Do not copy the files. relay.js is an ES module and is always fetched in CORS mode; this deployment serves it with the header that allows that. A copy without it gives you a blocked request, no error anyone reads, and a button that never appears — while the classic recorder.js beside it keeps working, which makes it look like a Relay bug.
Expecting the session to travel
The intake answers CORS without Allow-Credentials, deliberately, so a browser will not attach this deployment's cookie to your cross-origin press. The key is the credential on that path. A keyed report therefore has no reporter attached — if you want attribution, put your own user id in an adapter, where it is in your namespace.
A fixed-height app shell
If your layout is a shell at 100dvh with content in overflow:auto panes — every chat, inbox, dashboard and IDE — then document.scrollHeight is the fold. Relay expands those panes before the shutter and puts them back, and the browser test asserts the scroll position is unchanged afterwards. You do not have to do anything; this is here so the picture being taller than the window is not a surprise.
Reading the screenshot as a screen grab
It is not one. A clipped shadow, a substituted font or a grey box where a cross-origin image was is a rasterisation artifact, not the bug. Form values and scroll positions were copied across deliberately, so what the picture shows in a field is what was in that field. Every brief says this, in those words.

Where reports go

Routing connects Cursor, Claude, GitHub, Linear or a signed webhook with your own credentials, per workspace. Nothing is a default; a workspace with nothing connected still captures and still stores, and the console is still where a person reads them.

Redaction has three levels and the workspace’s setting is a floor: a reporter can ask for more than it and never for less.

Create a workspace