Portfolio Aperture

Record and replay for backend services

Reproduce a production bug on your laptop in under a minute

Aperture records every call, query, and job your service makes, then replays one of them against the code you are editing.

Free for 14 days on up to 5,000 replays, no card

checkout-api
$ aperture replay tr_8f21c4
→ 41 spans, 3 services, 1.2 MB
→ clock pinned 14:02:11.338Z
→ seeds restored

  POST /checkout/confirm   500
   | db.carts.find id=$1 → 1 row
   | flag:split_tender = on
   | job:reprice ran 92 s earlier
   | charge.create  timeout 30 s

breakpoint at pricing.rb:212

Fyre Logistics cut the median time to reproduce a customer-reported bug from three days to 41 minutes, over 214 incidents. Priya Raman, staff engineer, Fyre Logistics

A line number is not a cause

Your error tracker names the line that threw. It does not name the row that sat in the table, the header the caller sent, or the job that ran ninety seconds earlier and left the cache in the shape that broke it. Those four facts are the bug. The line is where it surfaced.

11 calls behind the median failing endpoint at Fyre, across four services

The recording is the reproduction

A trace is a file. Attach it to the ticket. Whoever picks the ticket up starts where you stopped, with the same rows, the same clock, and the same flag values you had.

# the trace, as it arrives in the ticket
tr_8f21c4.aperture   1.2 MB   captured 14:02:11.338Z   retention 30 d

  service  checkout-api  v2026.31.4   commit 9d3ac1e
  entry    POST /checkout/confirm     status 500      842 ms
  tenant   meridian-freight-intl
  redacted card_number, cvv, authorization header
Values matching a redaction rule never leave the machine that recorded them; the trace carries the field name and a length.

1.2 MB median trace, compressed. Bodies over 256 KB are stored by hash and fetched on replay

0 lines of instrumentation. Aperture attaches to the runtime, not to your code

Replay moves one thing at a time

Edit the code, replay the same trace, and the only variable that moved is the one you moved. Outbound calls answer from the recording, so a third party being down on Tuesday does not change Tuesday's answer.

What one recording holds

  • Inbound calls, headers, and bodies
  • Outbound calls and their answers
  • Database calls and bound values
  • Queue jobs and their payloads
  • Cache reads, writes, and misses
  • Feature-flag values as they read
  • Clock reads, to the millisecond
  • Random seeds and UUID draws
  • File reads under the app root
  • Environment values, names only
  • Thread and fiber scheduling order
  • Memory high-water mark per span

1.4 %median CPU cost while recording

42 msto first span on replay

30 daysretention, then the trace is gone

6runtimes supported, Ruby through .NET

Pay for what you replay

Every plan records without a meter. You pay for a replay once, whoever starts it.

Seats renew on the day you start and you can cancel any one of them at any time. Cancelling stops the next charge and leaves the seat working to the end of the month you have already paid for. Self-hosted runs for a year and renews the same way.

Drag to see what each plan costs at your volume. Overage lands on the next invoice.

Solo

$19a seat, a month

Replays included
5,000
Overage a replay
$0.010
Retention
7 days
Seats
1

Team

Recommended

$79a seat, a month

Replays included
50,000
Overage a replay
$0.006
Retention
30 days
Seats
2 – 40
Your workspace
Meridian Freight Systems International
Seats in use
12 of 40

Self-hosted

$4,800a year, unmetered

Replays included
Unmetered
Overage a replay
None
Retention
Yours
Seats
Unlimited

The first replay is the one that decides it

Install the agent on one service and record a single endpoint. If the first bug you replay already has a fix, you lose an afternoon.

Trial
14 days
Replays
5,000
Card
Not asked for
Services
One is enough

Design notes

Shape of the page
An editorial spread. Six blocks in five different column rhythms, with the argument hanging its numeral in one margin and its evidence in the other.
What it leads with
Sentences. Every figure on the page sits inside a sentence, or in a margin line that names what it counts.
Spacing
Generous, and broken deliberately in one place: the recording list is set close, so that the space between movements does the separating instead.
The one bold decision
Every colour on the page was measured before it was used. None of Tailwind's popular defaults survived: not the stock indigo, not the pale grey body text, not the pale grey control border.
Where the colour comes from
Tailwind's own published palette, with each step checked against the surface it lands on, and the six defaults that are known to fail deliberately kept off the page.
Type
The three families Tailwind ships and nothing bought in: a sans for the interface, a serif for the movement numerals, and a typewriter face for every figure, set so columns of numbers line up.
Readability, measured. Higher is easier to read, and 4.5 is the accessibility floor.
What sits on whatCarriesRatio
White on deep tealthe main button7.55:1
Grey on whiteevery muted line7.56:1
Teal on whitelinks, numerals, focus ring5.44:1
Pale grey on near-blackthe dark band16.28:1
Light teal on near-blackthe dark accent13.71:1
Near-black on light tealthe dark main button13.71:1
Grey on near-blackthe dark muted line7.68:1
Red on whitethe over-volume state4.92:1

Two rules, not one. The faint line that separates rows is far too pale to read text against, so it separates rows and does nothing else. Muted text is a step darker than the accessibility standard strictly asks for, because a second, newer standard reads that lighter step as usable only at large sizes. And of Tailwind's twenty-six hues, only six are readable on white at the middle of their scale. Teal is not one of them, so this page uses it two steps deeper.