How-to › Use AI to do the integration

How to write a task prompt that gets an API integration done#

Name the spec, the auth scheme, the language, the test command, and the done criteria in one task prompt, then check the agent's result against them yourself.

Audience
API consumer
Level
beginner
Topic
Write an integration with a coding agent
Verified

The agent reports that the client is finished. The file parses, every export exists, and the tests you never mentioned fail on their first run. The prompt named the API and the language and left the rest to be inferred, and the agent inferred that done means compiles.

What you get

You will end up with a task prompt naming the spec, auth scheme, language, test command, and done criteria, with a linter for the prompt and a checker for the result. This is for you if you hand integrations to a coding agent one at a time.

Short answer

Write one prompt per integration with five named parts: the spec path, the auth scheme, the target language and file, the test command, and the done criteria. Make every criterion something you can run or read, and put the test command in it. A plan-first flow costs one round trip and catches scope drift before code exists. Check the agent’s final message against the criteria, then run the test command yourself.

You will need

A coding agent that is installed and signed in, and Node 22 or later for the sample. Verified 2026-09-25 against Node 22.22.2. The prompt here is the per-task one, not the repository’s instruction file. The Claude Code guidance puts what is true of every task in CLAUDE.md. This prompt carries what is true of one integration, and it goes in the first message of the session.

Approaches compared

ApproachWhen it fitsWhat it costs youWhen to pick something else
A single detailed promptA task whose operations and files you can list yourself before the agent startsEvery gap in the prompt is filled by the agent’s guess, and you meet the guess in the diffYou cannot name the files the change will touch
Aider architect modeAider, and a change you want proposed by one model and applied by anotherTwo models per message, and the proposal is applied without a pause unless --auto-accept-architect is turned offYou want to read the plan before any edit happens
Claude Code plan modeClaude Code, and a change across several files or in code you do not knowOne extra round trip and a plan to read, which its own documentation says to skip for a change you can describe in a sentenceThe scope is clear and the fix is small
Gemini CLI plan modeGemini CLI, and research you want done read-only before any file changesA plan written to a Markdown file for you to approve or edit, with only read tools allowed until you doA one-line change, or a session that already knows the code

A single prompt is the cheapest flow and the one where every omission becomes a guess. Plan-first flows add a round trip. Claude Code and Gemini CLI both block edits until a plan is approved, and Aider’s architect mode has a proposer and an editor but applies the proposal by default.1 The round trip buys you the operation list and the file list before any code exists, which is where scope drift is cheapest to catch.

Name the five things the agent would otherwise guess

The prompt is a Markdown file with five headings. Each one closes a gap that the agent would otherwise fill on its own, and the last one is the one that gets left out.

## Spec

`openapi.json` in this directory. Implement every operation under `paths`: `listReadings`,
`getReading` and `createReading`. Do not add an operation the document does not have, and do
not change the document.

## Auth

The `apiKey` security scheme: every request carries the key in the `X-API-Key` header. The
client takes the key as a constructor argument and never logs it or puts it in a URL.

## Language

JavaScript on Node 22, ES modules, the runtime's `fetch`. One file, `client.mjs`, exporting
`createClient({ baseUrl, apiKey })`. No new dependencies.

## Test command

`node --test client.test.mjs`. The tests start `mock-api.mjs` on a free port and run the
client against it. Run them yourself before you report; do not edit them.

## Done when

- `node --test client.test.mjs` exits 0.
- `client.mjs` exports `createClient`, and the object it returns has `listReadings`, `getReading` and `createReading`.
- `package.json` has no dependencies.
- A response outside the 2xx range is thrown as an error whose message names the method, the path and the status.
- Your final message lists each operation implemented and pastes the last twelve lines of the test output.

The spec section names a path and the operations in it, and it forbids inventing more. The auth section names the scheme by the key the document uses, apiKey, and says where the credential comes from. The language section names the runtime, the module system, the one file to write, and the dependency budget, which is zero. The test command is a command, in backticks, that the agent runs and you run. Anthropic’s own guidance on being clear and direct is the rule behind all five: say what you want, and say it in terms a colleague with no context could follow.2

The done criteria are the part that decides where the agent stops. Three of the five are commands or files a script can check. The fourth is behavior the tests exercise. The fifth asks for evidence in the final message, which is what you read first.

A prompt linter holds the file to that shape before it is sent. It checks that the spec path exists and parses as OpenAPI, and that the auth section names a scheme the document declares. It reads JSON only, because Node has no YAML parser built in and the prompt allows no dependencies. A YAML spec gets a finding rather than a pass. It checks that the test command’s executable is on the PATH, and that no criterion rests on a word like “works”:

  if (s['done when']?.trim()) {
    const items = bullets(s['done when'])
    if (items.length < 2) findings.push('done when: fewer than two criteria')
    if (testCommand && !items.some((b) => b.includes(testCommand))) findings.push('done when: no criterion names the test command')
    for (const b of items) {
      const m = IMPLICIT.exec(b)
      if (m) findings.push(`done when: "${b}" is not checkable, "${m[0]}" describes a feeling`)
    }
  }

Run against the prompt above and against the kind of prompt that produces a client that compiles:

node demo.mjs
task-prompt.md
  5 sections present, every criterion checkable

vague-prompt.md
  no "spec" section
  no "auth" section
  no "language" section
  no "test command" section
  done when: "The client works correctly." is not checkable, "works" describes a feeling
  done when: "Auth is handled as expected." is not checkable, "handled" describes a feeling

The vague prompt is eleven lines and reads fine. Every finding against it is a decision the agent was about to make instead of you.

Ask for a plan first when the scope is not yours to state

When you cannot list the files a change will touch, spend one round trip on a plan. Start Claude Code in plan mode and send the same prompt:

claude --permission-mode plan

Claude reads files and proposes a plan and makes no edit until the plan is approved. Read the plan against the prompt. The operations it lists are the three under paths and no more, the files it will write are client.mjs and nothing else, and the test command it names is yours. A plan that adds a retry helper or a fourth operation is scope drift caught for the price of one message. Gemini CLI does the same with gemini --approval-mode=plan, writing the plan to a Markdown file you can edit before approving it.3 Aider’s architect mode has a proposing model and an editing model, and pauses between them only when told to:

aider --architect --no-auto-accept-architect --test-cmd "node --test client.test.mjs" --auto-test

The --test-cmd and --auto-test switches make Aider run your test command after every edit and try to fix a non-zero exit, which turns the second criterion into the agent’s own loop rather than yours.

Check the result against the criteria, not the message

The agent’s final message says done. The checker reads the same “Done when” list and runs what it can. A criterion that starts with a command is run and its exit code read. The checker splits the command the way a shell does, honoring quotes and backslash escapes, and runs it without a shell, so variables and globs pass through unexpanded. Node 23 changed the default reporter for piped output from TAP to spec, so the checker reads the pass and fail counts in either format. A criterion that names exports is checked by importing the file, and one about package.json by reading it, so a file that is missing or does not parse fails. Anything else is listed for you to check against the message.

    if ((m = RUNS.exec(item))) {
      // Split as a shell splits, quotes and backslashes honored, then run with no shell, so
      // nothing is expanded. A quote left open fails here as it would fail in a shell.
      const words = argv(m[1])
      if (!words) {
        results.push({ item, ok: false, detail: 'the command leaves a quote open' })
        continue
      }
      const [exe, ...args] = words
      // When this runs inside `node --test`, the runner marks the environment, and a nested
      // runner that inherits the mark reports as a subtest and exits 0 whatever happened.
      const { NODE_TEST_CONTEXT, ...childEnv } = env
      const run = spawnSync(exe, args, { cwd: dir, env: childEnv, encoding: 'utf8' })
      // A program that cannot start, most often one not on the PATH, fails the criterion.
      if (run.error) {
        results.push({ item, ok: false, detail: `${exe} did not start: ${run.error.code ?? run.error.message}` })
        continue
      }
      // Piped, Node 22 reports in TAP, `# pass 1`, and Node 23 and later in the spec format,
      // `ℹ pass 1`. Both are read, and the counts are reported in TAP's form either way.
      const summary = (run.stdout + run.stderr).split('\n').map((l) => l.replace(/^ℹ /, '# ')).filter((l) => /^# (pass|fail) /.test(l)).join(', ') || `exit ${run.status}`
      results.push({ item, ok: run.status === 0, detail: `exit ${run.status}, ${summary}` })

The demo runs it twice: against client-stub.mjs, which is where an agent stops when done was left implicit, and against the finished client.mjs.

node demo.mjs
done criteria against the stub an agent stopped at
  FAIL  `node --test client.test.mjs` exits 0.                        exit 1, # pass 1, # fail 4
  ok    `client.mjs` exports `createClient`, and the object it retur  4 names present
  ok    `package.json` has no dependencies.                           no dependencies
  hand  A response outside the 2xx range is thrown as an error whose  check by hand against the final message
  hand  Your final message lists each operation implemented and past  check by hand against the final message
  not done

done criteria against the finished client
  ok    `node --test client.test.mjs` exits 0.                        exit 0, # pass 5, # fail 0
  ok    `client.mjs` exports `createClient`, and the object it retur  4 names present
  ok    `package.json` has no dependencies.                           no dependencies
  hand  A response outside the 2xx range is thrown as an error whose  check by hand against the final message
  hand  Your final message lists each operation implemented and past  check by hand against the final message
  done, pending the by-hand items

The stub passes two of the three checkable criteria. A prompt without a test command would have asked for no more than the stub delivers: it parses, it imports, and all four names are present. One test of five passes against it. That is the gap between compiles and done, as a number.

For the by-hand items, take the final message from the agent rather than from your memory of it. In Claude Code, claude -p with --output-format json returns it in the result field, so the paste of the test output the fifth criterion asked for can be diffed against the output you produced yourself.

Check it worked

Thirteen tests cover the linter, the checker, and the mock API. The literal that matters is # pass 1, # fail 4 against the stub: a checker that reported the stub as done would be the failure this page exists to prevent.

node --test prompt.test.mjs
1..13
# tests 13
# suites 0
# pass 13
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 2000.781847

One test writes a client.mjs with a missing export and a package.json with a dependency. It asserts the checker fails both criteria, naming what is missing and counting what appeared. Another runs the linter and the checker from a directory with a space in its path and expects exit status 1 from each. A script that compared its own path with a URL there would skip its main block and exit 0, and a checker that exits 0 reads as done.

When it goes wrong

The agent stops at a client that compiles. The done criteria were implicit, or absent. Run the linter: a criterion it calls “not checkable” is one the agent got to interpret, and “works” is always read as “runs without throwing.”

The tests pass and the client is wrong. The agent edited the tests. The prompt says not to, and the exit code cannot tell. Diff client.test.mjs against the commit before the session before you trust the number.

The plan lists an operation the spec does not have. The spec section forbids it, and the plan is the cheapest place to say no. Reject the plan, quote the section, and ask again.

Aider applied the plan before you read it. --auto-accept-architect is on by default, and --no-auto-accept-architect is the switch that makes architect mode pause.

The final message pastes test output that does not match yours. The agent ran the tests at a point before its last edit, or ran a subset. The test command in the criteria is the one you run; the paste is evidence to compare, not a result to accept.

When not to do this

Do not put the per-task prompt into the repository’s instruction file. CLAUDE.md, GEMINI.md, and Aider’s conventions file are read on every session, and a done list for one integration becomes noise for the next fifty. The instruction file holds the house rules; the task prompt holds this task.

Do not ask for a plan on a change you could describe in one sentence. Claude Code’s own guidance says so, and a plan for a one-line fix is a round trip that buys nothing.

Do not let the agent write its own done criteria. It will write ones it can meet, and “compiles” is the one it can always meet. The criteria are yours, they are in the prompt before the first token comes back, and they do not move during the session.

Do not treat a pasted test summary as a test run. The paste is there so you can compare it with the run you make yourself. A paste with no run behind it is the failure mode the fifth criterion exists to expose. Run the command.

Last verified

Verified 2026-09-25 against Node 22.22.2. Every output block is what the command preceding it printed. No agent was run to produce the output: client-stub.mjs is a hand-written stand-in for the state an agent leaves a client in, and client.mjs is the finished client the prompt asks for. The plan-mode and architect-mode commands are the ones each tool’s documentation gives.

Footnotes

  1. Aider’s options reference lists --auto-accept-architect with a default of True. So the mode named for the person who draws the plans hands them to the builder without a pause, unless the flag is negated. The modes page describes an architect model that proposes and an editor model that translates the proposal into edits, which is two models agreeing with each other by default. ↩︎ Back to text

  2. The prompting guide offers what it calls a golden rule. Show your prompt to a colleague with minimal context on the task and ask them to follow it; if they would be confused, so will the model. The five sections here are what that colleague would ask for, in the order they would ask. ↩︎ Back to text

  3. Gemini CLI’s headless reference gives the process four exit codes: 0 for success, 1 for a general error, 42 for an input error, and 53 for a turn limit exceeded. A coding agent that reports running out of turns as a distinct exit code has met the session that never finishes, and has given it a number. ↩︎ Back to text

Read this page as markdown · All how-to guides

Generate the client instead of writing it#

Retries, timeouts, pagination and auth are the same problems in every client. Voxgig generates them from your OpenAPI description, in 23 languages, from one model.

Get the Voxgig dispatch

Short notes on building SDKs, CLIs, REPLs, and MCPs for API-first teams, plus the occasional Fireside episode pick.

By signing up you agree to our Terms and Conditions.