DEV Community

Cover image for I Reverse-Engineered an Undocumented API and Shipped 2 npm Packages in 4 Days — with Claude Code
yabbal
yabbal

Posted on

I Reverse-Engineered an Undocumented API and Shipped 2 npm Packages in 4 Days — with Claude Code

A freelancer's frustration, an undocumented API, and an AI pair-programmer. Here's how it went.


The spark: a freelancer's itch

I'm a developer, and like many freelancers in France, I use Tiime for my accounting. Invoices, bank transactions, categorization — everything goes through their web UI.

The problem? When you juggle multiple companies, you end up doing the same repetitive clicks every month. Checking uncategorized transactions, creating recurring invoices, auditing accounts... The kind of tasks that scream "automate me."

Except Tiime has no public API. No documentation. No SDK. Nothing.

So one Friday night, at 2:30 AM, I opened my terminal and typed:

"Build me a CLI for Tiime. Scan the entire app and map out every REST API endpoint."

My conversation partner? Claude Code. We didn't stop for 4 days.


Day 1 — Reverse-engineering the API

Sniffing the network

No Swagger, no docs — we had to intercept network requests straight from the frontend.

I opened Chrome DevTools, logged into Tiime, and let Claude analyze everything going through the wire. Within hours, we had mapped 40+ endpoints:

  • Auth: Auth0 with a password grant (not password-realm — a subtle distinction that cost us time)
  • Custom headers: tiime-app: tiime, tiime-app-version: 4.30.3, tiime-app-platform: web
  • Pagination: via the Range: items=0-25 header, with 206 Partial Content responses
  • Content negotiation: exotic Accept headers like application/vnd.tiime.bank_transactions.label_suggestions.v2+json

No documentation gives you these details. You discover them one request at a time.

The SDK takes shape

Once the API landscape was clear, I made a deliberate design choice: this project would be built for AI agents from the start. The CLI wouldn't just be a terminal tool — it would be exposed to Claude Code via a Skill, enabling natural language queries against Tiime.

That meant clean JSON output, atomic commands, and — most importantly — a reusable SDK separated from the CLI.

The first tiime auth login that actually worked, the first tiime bank transactions returning real amounts... That's the moment the project shifted from "is this even possible?" to "how far can we take this?"

CLI terminal showing bank transactions and audit commands

First dopamine hit: I asked Claude "Show me my latest transaction" — and through the Skill, it just answered.

Trust kicks in

At this point, I realized the collaboration was working. I started delegating aggressively:

"Go ahead, I trust you. Implement everything."

"Break down the work, spin up sub-agents in parallel, do whatever you want — just ship it."

Claude launched 6, 8, sometimes 10 sub-agents in parallel. One implementing invoices, another bank transactions, a third writing tests, a fourth building the retry system... My terminal exploded with activity.


Day 2 — The monorepo and the data leak

From monolith to monorepo

The project was growing fast. Too fast for a single package. Time to restructure into a Turborepo monorepo:

tiime/
├── packages/
│   ├── tiime-sdk/    # TypeScript SDK (zero dependencies)
│   └── tiime-cli/    # CLI (depends on the SDK)
├── apps/
│   └── docs/         # Fumadocs documentation
├── turbo.json
└── pnpm-workspace.yaml
Enter fullscreen mode Exit fullscreen mode

The SDK was designed to be as lightweight as possible: zero production dependencies, native fetch, final bundle of 11 KB. The CLI could afford dependencies like citty (CLI framework), @clack/prompts (interactive prompts), and cli-table3 (table formatting).

Turborepo handled build ordering automatically via the dependency graph: the SDK always builds before the CLI.

The incident that made me sweat

Then came the most tense moment of the entire project.

I had set up documentation with Fumadocs. Beautiful, clean, deployed on GitHub Pages. Until I noticed...

"You put my real personal data in the docs?! On a public repo?!"

Claude had used my real company names and real amounts in the documentation examples. On a public repository. With git history preserving everything.

Panic. We had to replace everything with dummy data and rewrite the git history to erase all traces. Force push on main. The kind of moment that reminds you: AI doesn't understand personal data — you have to be explicit.

Lesson #1: Always review what goes public. AI optimizes for realistic examples, not confidentiality.

The docs, after the scare

Once the incident was behind us, the documentation site turned out great. Fumadocs + Next.js as a static export, auto-deployed on GitHub Pages. Every CLI command and SDK method documented with examples — fictional ones this time.


Day 3 — The audit feature and the CI war

Multi-company audit

This was the feature I actually built the whole thing for: automated account auditing.

"I want a full audit of all my accounts. Detect what's uncategorized and auto-fix it across all my companies at once."

The tiime audit command scans all uncategorized transactions, suggests labels via Tiime's suggestion API, and can apply corrections automatically:

# Dry-run across all companies
tiime audit --all-companies

# Apply corrections
tiime audit --all-companies --apply
Enter fullscreen mode Exit fullscreen mode

This is where the API quirks bit us. The unimputed() endpoint sometimes returns just { id } — no wording, no amount. You have to re-fetch each transaction individually. The kind of silent bug that eats hours.

The CI/CD war

If I had to name the single recurring technical battle of this project, it's CI/CD. We iterated at least ten times on the GitHub Actions workflows:

  • Changesets for semantic versioning
  • Trusted Publishing (OIDC) for npm — no secret tokens, authentication via OpenID Connect between GitHub and npmjs.com
  • Provenance enabled for package traceability
  • Homebrew tap auto-updated on every CLI release
  • Coverage computed and displayed via a dynamic badge

Every single component broke at least once. Trusted publishing is tied to the workflow file name — rename ci.yml to release.yml and publishing silently fails. The Homebrew action wouldn't trigger because one workflow can't trigger another without workflow_dispatch.

"CI is broken again, check what's going on."

I typed that sentence at least 10 times in 4 days.

And sometimes frustration boiled over:

"That's wrong — the workflow is invalid, you didn't even check the syntax."

"The Homebrew workflow isn't even triggering!"

Lesson #2: CI/CD is where AI struggles the most. It can't test a workflow locally — it's pure trial-and-error, and each attempt takes several minutes.


Day 4 — The dashboard and Money Wrapped

A financial cockpit in one command

The idea came naturally: why not a visual dashboard?

"I want a financial dashboard. Not a separate Next.js project — just a CLI command that opens a web page with all the data."

The solution: tiime dashboard spins up a tiny HTTP server on port 3141 and opens the browser. The backend serves data through /api/* endpoints; the frontend is HTML/JS/CSS injected directly — no bundler, no framework, pure vanilla with Tailwind CDN and Chart.js.

Spotify Wrapped, but for money

Then came the wild idea:

"Add a Spotify Wrapped but for my finances. Storytelling with slides, stats, something fun."

And so Money Wrapped was born — 11 full-screen slides that tell your financial year's story:

Money Wrapped intro slide

  1. Intro — "Your financial year in review"
  2. Revenue — total + invoice count
  3. Best month — peak activity
  4. Top clients — ranked by revenue
  5. Cash flow — inflows vs outflows
  6. Monthly evolution — line chart
  7. Top expenses — main categories
  8. ...and more

Each slide has its own gradient, animations (incrementing counters, confetti, smooth transitions), plus keyboard, click, and touch-swipe navigation.

Mobile rendering was a battle. CSS scroll-snap refused to cooperate:

"The snap on mobile still doesn't work, it scrolls all over the place."

We ended up switching to a translateX approach with manual touch handling. Inelegant, but it works.


What I actually learned about pair-programming with AI

What works incredibly well

Parallelization. When I say "implement everything," Claude spins up sub-agents in isolated worktrees. One codes invoices, another writes tests, a third configures CI. Like having a team of 10 devs working simultaneously.

Reverse-engineering. For picking apart an undocumented API, AI is exceptional. It parses headers, detects pagination patterns, identifies data schemas — all faster than any human.

Boilerplate elimination. Configuring a Turborepo monorepo, writing TypeScript types, setting up CI/CD workflows — anything structural and repetitive, AI crushes.

What demands vigilance

Sensitive data. The documentation incident taught me the hard way: always review what goes public. AI optimizes for example quality, not confidentiality.

Cross-session memory. This was my biggest frustration:

"We literally solved this exact problem yesterday. Why don't you remember?"

"Why do I have to tell you to save important stuff? You should do that on your own."

Each new Claude Code session starts nearly from scratch. I learned to use the persistent memory system (MEMORY.md) to store API gotchas, architectural decisions, and CI pitfalls. But you have to be proactive about it.

CI/CD. AI can't test a GitHub Actions workflow before pushing it. It's guess-and-check. Each iteration takes 2-3 minutes. Over 10 iterations, that's 30 minutes of waiting for something an experienced DevOps engineer might nail in 2 tries.

Visual polish. For the Remotion video we attempted (and abandoned), or the Wrapped animations, AI produces correct output but rarely beautiful output on the first try. You need to iterate, guide, and sometimes say "no, not like that" several times.

The numbers

In 4 days, we shipped:

  • 107 commits
  • 2 npm packages published (tiime-sdk v3.0.1, tiime-cli v2.2.0)
  • 14 CLI commands with subcommands
  • 10 SDK resources (invoices, bank, clients, quotations, expenses...)
  • 1 documentation site on GitHub Pages
  • 1 financial dashboard with Money Wrapped
  • 1 Homebrew formula auto-updated
  • 1 Claude Code Skill for natural language queries
  • Unit tests with coverage

No solo developer sustains that pace. Not because the code writes itself — but because all the friction vanishes. The friction of looking up GitHub Actions syntax. The friction of remembering how to configure tsup. The friction of writing 400 lines of TypeScript types by hand.


The tech stack

Layer Choice Why
Monorepo Turborepo + pnpm Automatic build order, smart caching
Language TypeScript strict Type safety, DX, no any
SDK HTTP Native fetch Zero dependencies, 11 KB bundle
CLI framework Citty Lightweight, typed, nested subcommands
Prompts @clack/prompts Elegant interactive UX
Build tsup ESM-only, tree-shaking, minification
Linter Biome Replaces ESLint + Prettier in one tool
Tests Vitest Fast, native ESM compatible
Docs Fumadocs + Next.js MDX, static export, GitHub Pages
Dashboard Vanilla JS + Chart.js No framework, embedded in the CLI
CI/CD GitHub Actions + Changesets Trusted publishing OIDC, no npm secrets
Distribution npm + Homebrew npm i -g tiime-cli or brew install tiime

What I'd do again (and what I'd change)

Do again:

  • SDK-first architecture. Separating the SDK from the CLI from day one. The SDK can live on its own — in a Node.js script, a server, another CLI.
  • JSON-first output. Every command returns parseable JSON. That's what makes the Skill possible.
  • Trusted publishing. No npm tokens in GitHub secrets. OIDC is safer and cleaner.
  • The Claude Code Skill. Being able to say "what's my latest transaction?" in natural language and getting an answer — that's the real unlock.

Change:

  • Document gotchas from day 1. I should have maintained a living file of API quirks from the start, not after wasting time rediscovering the same issues.
  • Write tests earlier. We added them on day 3. When AI generates code fast, regressions arrive just as fast.
  • Build CI/CD incrementally. Instead of configuring everything at once (lint + build + test + release + Homebrew + coverage + docs), I should have added each piece one at a time.

Conclusion: AI as an amplifier, not a replacement

This isn't a story of "AI did everything." It's a story of intense pair-programming where each side brings what they're best at.

Me: the product vision, architectural decisions, quality control, and domain knowledge — what is transaction categorization? Why does multi-company auditing matter?

Claude Code: execution speed, encyclopedic knowledge of tools, the ability to parallelize, and infinite patience with CI/CD iterations.

The result? A tool I actually use every day to manage my accounting. A tool that didn't exist 4 days ago. A tool that, through its Skill, lets other AI agents interact with Tiime.

The code is open source. The SDK is on npm. The CLI installs via Homebrew.

And the next article? Probably the one where Tiime finally replies to my Instagram DM.


Built in 4 days (March 7-10, 2026). 107 commits. 2 npm packages. 1 developer. 1 AI. Too much coffee.

The project: tiime-cli on GitHub

Top comments (0)