Claude Code is not the Claude API: what MissionPilot taught me by combining them
Claude Code is the tool I write this blog with. The Claude API is what MissionPilot, the tool that scores my freelance opportunities found on LinkedIn, Malt and Comet, calls in production, every time a user loads a new listing. Both use the same model, but they solve two completely different problems, and confusing them when designing a product leads to bad architecture decisions.
Two execution contexts, not a nuance#
Claude Code runs in my terminal, with access to my filesystem, my tools, my personal configuration. The Claude API runs server-side in MissionPilot, called by code I wrote, with no access to anything beyond what that code explicitly passes it in the request. The difference isn't one of degree, it's one of kind: one is a development environment augmented by an agent, the other is a service a product consumes the way it would consume any third-party API.
What that changes for prompt design#
| Claude Code | API in MissionPilot | |
|---|---|---|
| Who writes the prompt | Me, whenever I want, iteratively | The code, on every request, identically |
| Available context | Repo files, session history | Only what the code explicitly passes |
| Tolerance to variability | High, I can rephrase if the result doesn't fit | Low, the output must be machine-usable |
| Expected output format | Free text, code, discussion | Structured, generally schema-validated JSON |
The most important row is the last one. A badly formatted mission score silently breaks the display if nothing catches it: asking for structured output, with an explicit schema, isn't a convenience option on the API side, it's what makes the result usable by code without human supervision on every call.
A typical API call#
const response = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
system: "You score a freelance mission offer from 0 to 100 against the "
+ "user's stack. Respond only with valid JSON.",
messages: [
{
role: "user",
content: `Stack: ${userStack}\n\nOffer: ${missionDescription}`,
},
],
});Nothing exotic in that call. What separates a prototype from a reliable component of MissionPilot happens elsewhere: in validating the output, handling formatting errors, and how the product behaves when the call fails or returns something unexpected.
Structured output isn't a detail#
Never trust JSON generated by an LLM without validating it against an explicit schema before using it. A model can produce syntactically valid but semantically incomplete JSON (a missing field, an unexpected type), and it's almost never a prompting problem, it's a missing-validation problem on the code side.
On MissionPilot, every response goes through schema validation before reaching the interface. A score outside expected bounds, a missing field, and the response gets rejected rather than displayed as is. That's not paranoia, it's the difference between an API call that runs once in testing and one that runs every day unsupervised.
Cost, a design constraint from day one#
A Claude Code session I pay for once, in personal usage tokens. An API call inside MissionPilot repeats for every scanned offer, for every user. Per-call cost becomes a design constraint from the start: limiting the size of the context sent to what's genuinely needed for scoring, rather than shipping an entire raw job listing out of implementation convenience.
What this generalizes to#
Building a product on the assumption that the API will behave like Claude Code in an interactive session, with room to rephrase and iterate, leads to systematically underestimating the validation and error-handling work needed server-side.
The overlap between Claude Code and the Claude API stops at the underlying model. Everything else, handling uncertainty, output format, cost, error tolerance, depends entirely on the execution context. Designing a product that calls the API without having thought through those constraints upfront means treating a production service like an interactive terminal, and that shows up as bugs that never appear in manual testing, only in production.
