Resumi takes your resume and a job posting and rewrites the bullets to match. The interesting problem is not the rewriting — the model is good at that. It is getting the result back in a shape the rest of the application can rely on.
Parsing prose is a trap
My first version asked for JSON in the prompt and parsed whatever came back. It worked most of the time, which is the worst possible outcome — it worked well enough that I built on top of it before I understood how it failed.
The failures were never malformed JSON. They were well-formed JSON with invented fields, or the right fields with subtly wrong types — a string where I expected an array, a single object where I expected a list of one. Every one of those reached the UI as an empty section rather than an error, because a missing key and an empty result look identical once you are three layers deep in optional chaining.
Forced tool-use
The fix was to stop asking and start constraining. Claude's API lets you define a tool with a JSON schema and require that the model responds by calling it. The response is validated against that schema before it reaches you. Off-schema output is not something you detect and handle — it is something that cannot be returned.
Resumi has four call modes, each with its own schema and its own endpoint: extract pulls structure out of a job posting, extract_resume does the same for an uploaded resume, tailor rewrites bullets against a target, and instruct applies a specific user edit. Four narrow contracts instead of one prompt trying to be everything.
The prompt is a boundary, not a suggestion
Schema constrains the shape. It says nothing about what the model is allowed to change. Nothing in a schema stops it inventing a job you never had, in perfectly valid JSON.
So the system prompt is 44 lines and almost all of it is prohibitions: rewrite phrasing, never invent employers, never change dates, never add a skill that does not appear in the source. Explicit content boundaries, not style guidance. The schema enforces the container; the prompt enforces the contents.
Testing without paying for it
Every end-to-end test hitting the real API costs money and takes seconds, which means you write fewer of them, which means you catch less. Resumi has a mock mode behind an environment variable:
RESUMI_MOCK=1 npm run devEvery call mode returns a fixed, schema-valid fixture. The whole application — upload, extract, tailor, render to LaTeX, download — runs end to end for free, in milliseconds. It catches the class of bug that actually bit me, which was never the model being wrong. It was my code mishandling a shape it did not expect.
What I would tell myself
If the model's output feeds anything other than a screen a human immediately reads, do not parse it. Constrain it. The parsing version is faster to build and you will spend that time back, with interest, on failures that look like empty states instead of errors.