Yellorn: The Browser Tab I Wanted for Dirty Data and Webhook Debugging
A human tour of Yellorn: repair broken JSON, XML, YAML, and CSV, publish mock webhook APIs, send HTTP requests, and keep integration debugging in one focused browser workspace.
The Short Version
Yellorn is the browser tab I wanted whenever an integration session starts with “this should only take five minutes” and ends with six formatter websites, a dead tunnel, and a CSV file that looks innocent until Excel gets involved.
It repairs broken JSON, XML, YAML, and CSV. It converts between formats. It can publish the current payload as a temporary mock webhook API. It records incoming webhook requests. It also sends outbound HTTP requests with saved templates and history.
Basically, it handles the small plumbing jobs around integration work. Not the heroic architecture work. The other kind. The kind that quietly eats your afternoon while everyone thinks you are “just testing the API.”
scene width=1280 height=720 bg=#f8fafc duration=6
actor title = caption("This was supposed to take five minutes") at top opacity 0
actor dev = figure(#c68642, m, 😵) at (180, 430)
actor json = text("JSON fixer") at (360, 180) size 34 opacity 0
actor csv = text("CSV converter") at (610, 260) size 34 opacity 0
actor tunnel = text("dead tunnel") at (830, 180) size 34 opacity 0
actor rest = text("REST client") at (520, 430) size 34 opacity 0
actor diff = text("diff tool") at (780, 430) size 34 opacity 0
actor yellorn = text("Yellorn") at (560, 570) size 64 opacity 0
@0.0: dev.enter(from=left, dur=0.7)
@0.2: title.fade_in(dur=0.4)
@0.8: json.fade_in(dur=0.3)
@1.2: csv.fade_in(dur=0.3)
@1.6: tunnel.fade_in(dur=0.3)
@2.0: rest.fade_in(dur=0.3)
@2.4: diff.fade_in(dur=0.3)
@2.8: dev.face("😫")
@2.8: camera.shake(intensity=8, dur=0.4)
@3.4: yellorn.fade_in(dur=0.5)
@3.8: yellorn.scale(to=1.2, dur=0.4, ease=out)
@4.4: dev.face("😎")
@4.4: dev.say("One tab is enough", dur=1.2)
The Five-Minute Task That Was Not Five Minutes
A familiar story: you ask an LLM for a JSON example. It gives you something that looks like JSON, except it is wrapped in Markdown, has a friendly sentence at the top, and ends an object with a trailing comma because apparently commas have feelings too.
It is usually something like this:
Here is the payload you asked for:
```json
\{
user_id: 42,
"email": "alice@example.com",
"active": True,
\}
```
Then a backend log gives you a Python dict:
{'ok': True, 'price': Decimal('3.14'), 'created_at': Timestamp('2026-05-02')}
Then someone sends a CSV from Excel. It has a UTF-8 BOM, semicolons instead of commas, and smart quotes. Nobody admits touching anything. The file simply “came like that”, which is also how most production incidents introduce themselves.
id;name;status
1;"Alice";active
2;"Bob";failed
And while you are cleaning that up, a vendor asks for a webhook URL. Your local tunnel has changed. Again. You paste the new URL into their dashboard, click test, get nothing, and spend ten minutes debugging the ancient distributed system known as “did I remember to press Save?”
None of this is hard. That is what makes it annoying. Hard problems at least have dignity. This is just losing focus one paper cut at a time.
Yellorn is built for that part.
First, Fix the Data Without Pretending It Was Fine
Strict parsers are correct to be strict. JSON.parse should not become a therapist for whatever came out of Slack, Claude, Excel, and someone’s clipboard history.
But a developer tool can still help before giving up.
Yellorn starts strict. If the input is valid, it formats it and moves on. If it fails, Data Doctor tries a documented recovery path across JSON, XML, YAML, and CSV. There are 29 auto-fix passes today: 15 for JSON, 5 for XML, 6 for YAML, and 3 for CSV.
The JSON repairs cover the usual suspects: Markdown fences, comments, Python literals, single quotes, unquoted keys, trailing commas, missing commas, auto-closing brackets, timestamps, datetime, Decimal, tuples, and sets. CSV gets help with things like BOM cleanup, delimiter weirdness, and quote normalization. XML and YAML get their own repair passes too, because apparently every format found a unique way to ruin a morning.
The important part is not just that Yellorn fixes things. It tells you what it fixed.
For the messy LLM paste above, the useful result is not drama. It is just clean data you can inspect:
{
"user_id": 42,
"email": "alice@example.com",
"active": true
}
If it strips a Markdown fence, normalizes smart quotes, removes a trailing comma, or closes a bracket, the fix is labeled. That matters. A tool may help with syntax. It should not quietly rewrite meaning and then smile at you like everything is fine.
Bad commas are Yellorn’s problem. Bad business logic is still yours. Sorry. Software remains unfair.
scene width=1280 height=720 bg=#fff7ed duration=7
actor title = caption("Data Doctor cleans the syntax mess") at top opacity 0
actor dirty = text("LLM paste with fences and True") at (120, 160) size 34 opacity 0
actor line1 = text("user_id: 42") at (170, 260) size 30 opacity 0
actor line2 = text("active: True") at (170, 310) size 30 opacity 0
actor line3 = text("trailing comma") at (170, 360) size 30 opacity 0
actor doctor = figure(#c68642, m, 🤓) at (610, 420)
actor clean = text("valid JSON") at (850, 200) size 44 opacity 0
actor fixed1 = text("user_id quoted 42") at (860, 290) size 30 opacity 0
actor fixed2 = text("active becomes true") at (860, 340) size 30 opacity 0
actor receipt = text("fixes are labeled") at (780, 500) size 32 opacity 0
@0.0: title.fade_in(dur=0.4)
@0.3: dirty.fade_in(dur=0.4)
@0.7: line1.fade_in(dur=0.2)
@0.9: line2.fade_in(dur=0.2)
@1.1: line3.fade_in(dur=0.2)
@1.4: doctor.enter(from=bottom, dur=0.6)
@2.2: doctor.face("🧐")
@2.2: dirty.shake(intensity=10, dur=0.5)
@3.0: doctor.wave(side=right, dur=0.7)
@3.8: clean.fade_in(dur=0.3)
@4.1: fixed1.fade_in(dur=0.2)
@4.3: fixed2.fade_in(dur=0.2)
@4.8: receipt.fade_in(dur=0.4)
@5.4: doctor.face("😎")
@5.4: doctor.say("Syntax fixed meaning untouched", dur=1.2)
Then Convert It Without Rebuilding the Shape by Hand
Once the payload is parsed, the next question is usually: “Can I see this as another format?”
Backend wants Python. Frontend wants JSON. Ops wants YAML. Business sends CSV. A legacy system still speaks XML, possibly out of principle. Nobody is wrong, exactly. But the person in the middle still has to move data around.
Yellorn converts between JSON, XML, YAML, CSV, and Python-style output. CSV to JSON becomes rows of objects. XML to JSON keeps conventions like @_attr, #text, and #cdata. Python export can preserve useful types such as datetime.fromisoformat(...), Decimal, tuples, and sets.
Some conversions are naturally lossy. Deeply nested JSON does not become a nice CSV just because we ask politely. When a conversion may reshape or lose information, Yellorn warns before doing it.
Also, undo is atomic: one undo can restore both the text and the active format. That sounds small until the day a tool changes the data and the UI state separately and you discover that Ctrl+Z has a sense of humor.
scene width=1280 height=720 bg=#eef2ff duration=7
actor title = caption("One parsed value many useful formats") at top opacity 0
actor csv = text("CSV") at (140, 200) size 52 opacity 0
actor json = text("JSON") at (140, 420) size 52 opacity 0
actor value = text("parsed value") at (510, 310) size 56 opacity 0
actor xml = text("XML") at (920, 200) size 52 opacity 0
actor yaml = text("YAML") at (920, 420) size 52 opacity 0
actor warning = text("lossy conversion gets a warning") at (390, 570) size 34 opacity 0
@0.0: title.fade_in(dur=0.4)
@0.4: csv.enter(from=left, dur=0.6)
@0.8: json.enter(from=left, dur=0.6)
@1.4: value.fade_in(dur=0.5)
@2.2: xml.enter(from=right, dur=0.6)
@2.6: yaml.enter(from=right, dur=0.6)
@3.4: camera.zoom(to=1.18, dur=0.5, ease=out)
@4.0: warning.fade_in(dur=0.4)
@4.6: warning.shake(intensity=5, dur=0.4)
@5.2: camera.zoom(to=1.0, dur=0.5, ease=out)
Publish a Webhook When You Just Need a Real URL
Sometimes you do not need a new service. You need a URL.
With Yellorn, you paste a payload, click Publish, and get a Cloudflare-edge HTTPS endpoint:
https://yellorn.com/api/webhook/<slug>
Which means the next test can be as boring as it should be:
curl -X POST "https://yellorn.com/api/webhook/<slug>" \
-H "Content-Type: application/json" \
-d '{"event":"invoice.paid","customer_id":"cus_123"}'
All HTTP methods are accepted at the slug. You can configure the response status to simulate 200, 401, 429, or 500, which is useful when you need to test success, auth failure, rate limiting, or the classic “server is having a moment” path.
The endpoint is public to anyone with the slug. That is intentional. Webhooks need to be callable from outside. The request log portal at /webhook/<slug> is different: it is owner-only. People can send requests in; they cannot read your logs.
The portal shows method, headers, body, source IP, and timestamp for incoming requests. Yellorn also serves the right content type for the format you publish: XML as application/xml, CSV as text/csv, and so on.
The free tier gives you 5 active URLs with a 24-hour TTL. Pro and Team raise the limits for heavier usage.
This is not a production backend. It is the thing you use before writing a backend, or when writing one would be a very elaborate way to return a sample payload.
scene width=1280 height=720 bg=#ecfeff duration=7
asset packet = icon("noto:incoming-envelope")
actor title = caption("Publish payload get a real webhook URL") at top opacity 0
actor dev = figure(#c68642, m, 😎) at (170, 440)
actor endpoint = text("https://yellorn.com/api/webhook/slug") at (330, 260) size 34 opacity 0
actor vendor = figure(#8d5524, m, 🤖) at (1040, 440)
actor log = text("owner only request log") at (420, 520) size 38 opacity 0
actor status = text("200 401 429 500") at (510, 360) size 44 opacity 0
@0.0: title.fade_in(dur=0.4)
@0.2: dev.enter(from=left, dur=0.6)
@0.4: vendor.enter(from=right, dur=0.6)
@1.2: endpoint.fade_in(dur=0.5)
@1.8: dev.say("Send it here", dur=1.0)
@2.6: vendor.throw(packet, to=endpoint, dur=0.8)
@3.4: endpoint.shake(intensity=6, dur=0.3)
@3.8: log.fade_in(dur=0.4)
@4.4: status.fade_in(dur=0.4)
@5.0: vendor.face("✅")
@5.2: camera.zoom(to=1.08, dur=0.5)
Send the Other Half of the Integration
Webhook debugging has two directions. Something calls you, and you call something else.
Yellorn includes a Request Sender for the second half. It supports GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS, with configurable URL, headers, auth, and body.
You can save requests as templates, then come back later without reconstructing the same headers from memory. Each dispatch records history: status, timing, response headers, and response body truncated up to 1 MB.
That history is surprisingly useful. Humans are bad log stores. We compress everything into “I think it returned 401?” and then confidently waste twenty minutes.
The sender also has guardrails: SSRF protection, loop protection, and an X-Yellorn-Internal-Dispatch header to avoid recursively calling your own Yellorn webhook. Convenience tools should still have brakes. A car with only an accelerator is called a postmortem.
scene width=1280 height=720 bg=#f0fdf4 duration=7
asset bolt = icon("noto:high-voltage")
actor title = caption("Request Sender handles the outbound half") at top opacity 0
actor sender = text("saved request template") at (120, 250) size 38 opacity 0
actor api = text("vendor API") at (900, 250) size 48 opacity 0
actor history = text("status timing headers body") at (420, 520) size 36 opacity 0
actor guard = text("SSRF and loop guard") at (460, 610) size 30 opacity 0
actor dev = figure(#c68642, m, 🧐) at (230, 430)
@0.0: title.fade_in(dur=0.4)
@0.3: dev.enter(from=left, dur=0.6)
@0.8: sender.fade_in(dur=0.4)
@1.4: api.fade_in(dur=0.4)
@2.0: dev.throw(bolt, to=api, dur=0.8)
@2.9: api.shake(intensity=6, dur=0.3)
@3.4: history.fade_in(dur=0.4)
@4.2: guard.fade_in(dur=0.4)
@4.8: dev.face("😌")
@4.8: dev.say("Now I can replay it", dur=1.2)
Look at the Payload Like a Human
Raw text is great until the payload becomes six levels deep and you start counting braces with your eyes like a medieval punishment.
Yellorn gives you a few ways to inspect the same data:
- Tree view for nested JSON-like structures, with inline editing and quick copy actions.
- Table view for row-shaped data like CSV or arrays of objects.
- Graph view for parent-child relationships.
- JMESPath search when “find the failed users over 30” should not require expanding 200 nodes by hand.
For example:
users[?age > `30`].name
The point is not to make data look fancy. The point is to stop burning working memory on bracket navigation.
scene width=1280 height=720 bg=#faf5ff duration=7
actor title = caption("Same payload different views") at top opacity 0
actor raw = text("{ braces braces braces }") at (110, 190) size 40 opacity 0
actor tree = text("Tree") at (230, 460) size 52 opacity 0
actor table = text("Table") at (520, 460) size 52 opacity 0
actor graph = text("Graph") at (830, 460) size 52 opacity 0
actor query = text("users age over 30") at (420, 310) size 36 opacity 0
actor dev = figure(#c68642, m, 😵) at (1040, 430)
@0.0: title.fade_in(dur=0.4)
@0.4: raw.fade_in(dur=0.4)
@1.0: dev.enter(from=right, dur=0.6)
@1.8: raw.shake(intensity=10, dur=0.5)
@1.8: dev.face("😵")
@2.6: tree.fade_in(dur=0.3)
@2.9: table.fade_in(dur=0.3)
@3.2: graph.fade_in(dur=0.3)
@3.8: query.fade_in(dur=0.4)
@4.4: dev.face("😎")
@4.4: dev.say("I can read this now", dur=1.2)
The Small Workflow Stuff Matters Too
Yellorn also includes a few features that are not flashy, but save repeated setup:
- Compare mode opens two tabs in a read-only Monaco diff editor, with JSON sorted before diffing so key order does not create fake drama.
- Smart Format does heavier recovery and type conversion.
- Simple Format pretty-prints while preserving meaning.
- Safe Format adjusts layout without reparsing.
- Compress removes whitespace.
- Cloud Backup can sync tabs, palette, and layout across signed-in devices, with quota visibility.
None of these features deserves a keynote. Together, they make the workspace feel less like a random tool pile and more like a place where you can finish the debugging loop.
scene width=1280 height=720 bg=#fff1f2 duration=7
actor title = caption("Small workflow features keep the loop moving") at top opacity 0
actor smart = text("Smart Format") at (160, 190) size 38 opacity 0
actor simple = text("Simple Format") at (470, 190) size 38 opacity 0
actor safe = text("Safe Format") at (820, 190) size 38 opacity 0
actor compare = text("Compare tabs") at (250, 420) size 42 opacity 0
actor backup = text("Cloud Backup") at (700, 420) size 42 opacity 0
actor dev = figure(#c68642, m, 🙂) at (600, 570)
@0.0: title.fade_in(dur=0.4)
@0.5: smart.fade_in(dur=0.3)
@0.9: simple.fade_in(dur=0.3)
@1.3: safe.fade_in(dur=0.3)
@1.9: compare.fade_in(dur=0.3)
@2.3: backup.fade_in(dur=0.3)
@3.0: dev.enter(from=bottom, dur=0.6)
@3.8: camera.pan(to=(640, 360), dur=0.6)
@4.5: dev.wave(side=right, dur=0.8)
@4.5: dev.say("Less setup more debugging", dur=1.2)
What It Replaces, and What It Does Not
Yellorn replaces the annoying shuffle between a formatter, converter, webhook bin, tunnel, request sender, and diff tool for many everyday debugging tasks.
It does not replace Postman for complex team API workflows. It does not replace integration tests, schema agreements, observability, or a real backend. It also cannot make semantically wrong data correct. If the customer ID is wrong, Yellorn will not meditate over the payload and discover your domain model.
That boundary is deliberate. Yellorn is for the practical middle: clean the payload, convert it, publish a temporary endpoint, inspect incoming requests, send outbound ones, and keep moving.
scene width=1280 height=720 bg=#f8fafc duration=7
actor title = caption("Useful boundary no magic claims") at top opacity 0
actor yellorn = text("Yellorn") at (220, 250) size 58 opacity 0
actor does = text("clean convert publish inspect send") at (140, 360) size 32 opacity 0
actor not = text("not Postman not prod backend") at (700, 250) size 38 opacity 0
actor truth = text("wrong customer id stays wrong") at (700, 360) size 34 opacity 0
actor dev = figure(#c68642, m, 😎) at (600, 560)
@0.0: title.fade_in(dur=0.4)
@0.5: yellorn.enter(from=left, dur=0.6)
@1.2: does.fade_in(dur=0.4)
@1.8: not.enter(from=right, dur=0.6)
@2.5: truth.fade_in(dur=0.4)
@3.2: camera.zoom(to=1.1, dur=0.5)
@3.8: dev.enter(from=bottom, dur=0.6)
@4.5: dev.say("Practical middle only", dur=1.2)
@5.4: camera.zoom(to=1.0, dur=0.5)
If that is the kind of work you keep doing between “real” tasks, try Yellorn. It was built for the small integration chores that are too common to ignore and too boring to keep doing by hand.
Related posts
-
Production Google OAuth In One Prompt: I Let Cursor Drive Chrome DevTools MCP
I asked an AI agent to wire production-grade Google OAuth into yellorn.com. One prompt later, it had clicked through Google Cloud Console, configured the consent screen, registered the OAuth client, stored the secrets in Cloudflare, and shipped to prod. I just sipped coffee.
-
MoneyPrinterV2: What 18,000 Stars Worth of Automated Content Actually Looks Like
An assembly line for AI content — local LLMs write the script, KittenTTS reads it, Gemini paints the pictures. The video uploads itself.
-
Project N.O.M.A.D.: The Knowledge Bunker You Build for a Rainless Day
When the cloud evaporates, what stays on your disk matters.
-
ruanyf/weekly: The Digital Lighthouse in a Sea of Algorithmic Slop
At the end of the infinite scroll, one man is still standing.