Skip to content

Building an API with Codex works best contract-first: a person reviews a short OpenAPI 3.1 file, Codex implements one endpoint slice per task in its own Git worktree, and machine gates (Redocly lint, the test suite, Schemathesis fuzzing and codex review) prove the handlers match the contract. The reviewer reads the contract and the gate output, not every handler.

You asked Codex for “the invitations API” and got 1,400 lines across 19 files in one pass. The tests are green, but Codex wrote them in the same session as the handlers, so they agree with the code rather than with what the frontend team expects, and nobody can say whether a duplicate invitation returns 409 or 422 without reading the handler. This page is for the developer building the endpoints and the tech lead who has to approve them without reading all 19 files.

The cross-tool version of this method, with GraphQL, gRPC, versioning and load testing, is API development with AI. This page covers what is specific to Codex: how to split the work into Codex tasks, which Codex commands run each step, and where Codex’s sandbox changes the plan.

What you’ll walk away with from contract-first API work in Codex

Section titled “What you’ll walk away with from contract-first API work in Codex”
  • An AGENTS.md section that gives Codex your API conventions and the exact gate commands.
  • A five-step loop: contract, commit, one worktree task per slice, gates, review.
  • Three copy-paste prompts: draft the contract, implement a slice against it, and fix a contract-fuzz failure.
  • A gate script that checks the implementation against the spec without anyone reading the handlers.
  • The failure modes of agent-built APIs and how to recover from each.

What do you read, and what does the machine check?

Section titled “What do you read, and what does the machine check?”

The point of contract-first is to move your attention onto the smallest artifact that defines behaviour. A 150-line OpenAPI file for five endpoints is something a person can review properly. The 1,400 lines behind it are not.

ArtifactWho checks itHow
api/openapi.yaml (the contract)You, line by line, plus the API owner via CODEOWNERSPull request review of a small diff
Contract validityMachinenpx @redocly/cli lint api/openapi.yaml
Handlers, validation, queriesMachineType check, unit and integration tests
Handlers match the contractMachineschemathesis run against a running server
Risky logic (auth, money, data deletion)Codex reviewer, then you on flagged files onlycodex review --base main, steered by the ## Code Review Rules section of AGENTS.md

A person still signs off: the developer who owns the branch reads the contract diff and the gate output, and the API owner approves any change to api/openapi.yaml. What nobody does is read every handler to find out what the API does.

Give Codex your API conventions in AGENTS.md

Section titled “Give Codex your API conventions in AGENTS.md”

Codex reads AGENTS.md before it starts work, so conventions written there apply to every task without repeating them in prompts. First install the contract linter as a dev dependency with npm install -D @redocly/cli, so a sandboxed Codex session can run it without network access. Then add a section like this to the AGENTS.md at the repository root. The stack here is Express, TypeScript, Zod, Drizzle ORM and Vitest with supertest; replace the commands with yours.

## API conventions
- The contract is api/openapi.yaml (OpenAPI 3.1). It is the source of truth.
Implementation tasks never edit it. If the contract looks wrong, stop and say so.
- Handlers live in src/routes/<resource>/, business logic in src/services/,
queries in src/lib/db/queries/. Handlers stay thin: parse, call the service, map errors.
- Validate every request body and query with Zod before it reaches a service.
- Error body is always { "error": { "code": string, "message": string, "details"?: object } }.
400 = malformed request, 401 = no or bad credentials, 403 = authenticated but not allowed,
404 = not found or not visible to this caller, 409 = conflict with current state, 422 = valid shape, invalid values.
- List endpoints use cursor pagination: ?cursor=&limit= (default 20, max 100), response { items, nextCursor }.
- No console.log; use the logger from src/lib/logger.ts.
## API gates (run all before saying a task is done)
- npm run typecheck
- npm run lint
- npm test # Vitest, including supertest integration tests
- npx @redocly/cli lint api/openapi.yaml
## Code Review Rules
- Check every changed handler against api/openapi.yaml: status codes, error body shape,
required fields, auth on each route and pagination limits.
- Flag any endpoint without integration tests for its 401, 403 and 404 cases,
and any list or read endpoint without a cross-team access test.

Three things in that block do most of the work. The first rule makes the contract read-only for implementation tasks, which stops the agent from changing the spec to fit its code. The status-code list removes the 409 versus 422 guessing that otherwise differs from endpoint to endpoint. Codex reads custom review rules from AGENTS.md, which it loads on every review, including codex review in your terminal and @codex review on GitHub; ## Code Review Rules is the section name OpenAI’s GitHub guide gives for them (read 2026-10-12), and it keeps those rules in one place. For how to keep this file inside Codex’s instruction budget as it grows, see optimize AGENTS.md and skills for Codex.

Optionally, give Codex current library documentation through an MCP server, so it writes Zod 4 and Drizzle code against today’s APIs rather than older ones it remembers. Context7 is one such server (@upstash/context7-mcp 4.1.1 on npm, 2026-09-26):

Terminal window
codex mcp add context7 -- npx -y @upstash/context7-mcp
codex mcp list

Every connected server adds tool definitions to the context, so add it when stale library APIs are a real problem, not by default. Scoping and verifying servers is covered in set up MCP servers in Codex and the Context7 guide.

The example is a team-invitations API with five endpoints. Each numbered step says which Codex command runs it.

  1. Draft the contract in an interactive session. Create a branch such as api/invitations-contract, run codex in your repository and paste the first prompt below. Codex writes only api/openapi.yaml, lints it and lists its open questions. You answer the questions and edit the file until it describes the API you want. This is the one file you read line by line.

  2. Commit the contract on its own branch and get it approved. Open a pull request that contains only the contract. The frontend team or other API consumers review it here, while a change costs minutes. Merge it before any implementation starts, so every implementation task begins from an approved contract.

  3. Implement one slice per worktree task. A slice is one or two endpoints that ship together, for example “create and list invitations”. Start each slice from the merged contract in its own worktree, so parallel slices cannot overwrite each other:

    Terminal window
    git fetch origin
    git worktree add -b api/invitations-create-list ../myapp-inv-create-list origin/main
    cd ../myapp-inv-create-list
    npm ci # install in your own terminal: the workspace-write sandbox has no network
    codex

    codex --worktree creates a managed worktree instead, but it starts on a detached HEAD without your .env files or node_modules. The trade-offs and a bootstrap script are in isolated Codex tasks with Git worktrees. Paste the second prompt below, naming the slice.

  4. Run the gates yourself. When Codex reports the slice done, run the gate script from the next section in the worktree. Codex’s summary is a claim; the exit codes are the evidence.

  5. Review against the contract, then open the pull request. Run a Codex review of the branch. It reads AGENTS.md, including the review rules above, so it checks the handlers against the contract:

    Terminal window
    codex review --base main

    In Codex CLI 0.157.1, --base cannot be combined with a custom-instructions argument (codex review --base main "..." fails with an argument error), which is why the contract checks live in AGENTS.md rather than on the command line. Read only the files the review flags, fix or dismiss each finding, then push and open the pull request. CI reruns the same gates, as described in CI/CD with Codex. For the layered review setup (pre-push hook, @codex review on GitHub), see code review with Codex.

The “tests first, show me they fail” line matters. Tests written after the handler tend to assert what the handler does; tests written from the contract and seen failing first assert what the contract says. For why that makes a test suite worth trusting, see how strong is your oracle?.

How do you verify an API slice without reading every handler?

Section titled “How do you verify an API slice without reading every handler?”

Run this script from the slice’s worktree after Codex finishes, and in CI on every pull request; in CI, check out with full history (for example fetch-depth: 0) so origin/main...HEAD resolves. It stops at the first failure. Start the server against a disposable test database first, in another terminal (for example npm run dev:test), because a server started inside the agent’s sandbox is not something to rely on. The script probes $BASE_URL/health before fuzzing, so give the server a liveness route or point HEALTH_URL at one that exists.

#!/usr/bin/env bash
# api-gate.sh: prove an API slice matches api/openapi.yaml. Run from the worktree root.
set -euo pipefail
BASE_URL="${BASE_URL:-http://localhost:3000}"
# 1. A branch changes the contract or the implementation, never both.
CHANGED=$(git diff --name-only origin/main...HEAD)
if grep -qx 'api/openapi.yaml' <<<"$CHANGED" && grep -q '^src/' <<<"$CHANGED"; then
echo "contract and implementation changed on one branch: split the contract into its own PR" >&2
exit 1
fi
# 2. The contract is valid, and the code builds, lints and passes its own tests.
npx @redocly/cli lint api/openapi.yaml
if [[ "$CHANGED" == "api/openapi.yaml" ]]; then
echo "contract-only change: no handlers to test yet, stopping after the lint"
exit 0
fi
npm run typecheck
npm run lint
npm test
# 3. The running server matches the contract for this slice's operations: generated requests, every check.
OPERATIONS="${OPERATIONS:?set OPERATIONS to the slice operationIds, e.g. createInvitation,listInvitations}"
INCLUDE=()
IFS=',' read -ra IDS <<<"$OPERATIONS"
for id in "${IDS[@]}"; do INCLUDE+=(--include-operation-id "$id"); done
HEALTH_URL="${HEALTH_URL:-$BASE_URL/health}"
curl -sf "$HEALTH_URL" > /dev/null || { echo "server not answering at $HEALTH_URL" >&2; exit 1; }
schemathesis run api/openapi.yaml \
--url "$BASE_URL" \
"${INCLUDE[@]}" \
--checks all \
--header "Authorization: Bearer ${TEST_API_TOKEN:?set TEST_API_TOKEN to a test-user token}" \
--report junit --report-dir reports/schemathesis

What each stage catches:

  • Stage 1 stops a common way an agent “passes” a contract: editing the contract in the same branch as the code. Contract pull requests (step 2) change only api/openapi.yaml, so they pass Stage 1, and the script stops after the Redocly lint: a contract-only pull request has no handlers yet, so Stage 2 and Stage 3 have nothing to check. An implementation branch that touches the contract fails. Protecting the oracle explains why the check must live outside the agent’s reach, for example as a required CI job the agent cannot edit.
  • Stage 2 catches type errors, lint violations and failures in the tests Codex wrote. It cannot catch a test that asserts the wrong thing.
  • Stage 3 is the independent check. Schemathesis (4.28.0 on PyPI, 2026-09-26; install with pip install schemathesis or run it with uvx schemathesis) reads the spec, generates valid and invalid requests, and checks status codes, response schemas, content types and headers against it. Its ignored_auth check flags endpoints that still succeed when the credentials the spec requires are left out, and negative_data_rejection flags handlers that accept input the spec forbids. None of those checks came from Codex, so they do not share its blind spots. The OPERATIONS variable scopes the run to the slice under review with one --include-operation-id flag per id (Schemathesis 4.28.0 also accepts --include-path, --include-method and --include-tag). Without it, Schemathesis fuzzes every operation in the contract, and every slice pull request fails on the other slices’ endpoints, which return 404 until they are built. In CI, set OPERATIONS per pull request, for example from a label or a line in the pull request description.

Keep TEST_API_TOKEN in your shell or your CI secret store, never in the script. Point BASE_URL at a local or disposable environment only: Schemathesis sends a large volume of generated requests, including POST calls that create and change data.

When Stage 3 fails, do not fix the failure by hand and do not let Codex relax the check. Give Codex the report:

All steps run from the Codex CLI (0.157.1 on 2026-09-26). The other surfaces fit specific steps.

StepGood fitWhy
Draft the contractcodex (interactive) or the desktop appYou iterate on questions and edits together
Implement a slicecodex in a git worktree add checkout, or codex --worktreeIsolation per slice; see worktrees
Several slices at onceOne worktree per slice, merged in orderCoordination is covered in Codex multi-agent workflows
Reviewcodex review --base main, or /review inside a sessionApplies the custom review rules in AGENTS.md
Scripted checks in CIcodex exec with --output-schemaSchema-checked output a job can read; see run Codex non-interactively
Desktop threadscodex app (macOS and Windows)A worktree option per thread; see Codex desktop app mastery

Start on Codex’s default model, raise reasoning effort before you switch models, and switch only when your own results say so. The current default and alternatives are on the models hub.

Codex edits api/openapi.yaml to make its handlers pass. Stage 1 of the gate fails. Restore the approved contract with git checkout origin/main -- api/openapi.yaml, rerun the tests, and give Codex the failures with the reminder that the contract is read-only. If the contract really is wrong, fix it in a separate contract pull request and rebase the slice on it.

Zod schemas and the contract drift apart. Handlers validate with Zod, the contract says something else, and Stage 3 reports negative_data_rejection or positive_data_acceptance failures. Pick one source of truth. Either generate the Zod schemas from the contract, or generate the contract from Zod with a library such as zod-openapi and diff the output in CI. Do not maintain both by hand.

Tests pass in your checkout and fail in the worktree. A new worktree has no node_modules, no gitignored .env files and no test database. Run npm ci and your database setup in your own terminal before starting Codex; the workspace-write sandbox has no network, so an install Codex attempts will fail. A managed worktree from codex --worktree also starts on a detached HEAD: create a branch before Codex commits, or the commits belong to no branch.

Codex ignores the conventions in AGENTS.md. Since Codex 0.150.0, an untrusted project does not supply its project-level AGENTS.md. Confirm the project is trusted, then check what actually loaded with codex debug prompt-input, as shown in optimize AGENTS.md and skills for Codex.

Authorization holes that every test misses. Codex tests the roles you named and skips the ones you did not. The classic miss is an admin of team A reading team B’s invitations. Schemathesis’s ignored_auth check catches missing authentication, not wrong authorization, so ask for cross-tenant tests explicitly (the slice prompt above does) and keep the cross-team check in the ## Code Review Rules section of AGENTS.md.

Rate-limit tests are flaky. Tests that wait for a real time window fail at random on slow CI runners. Tell Codex to inject the clock and the limit through configuration, so a test sets the limit to 3, sends 4 requests and expects 429 with a Retry-After header, with no sleeping.

Schemathesis fails on the first request with 401. The token is missing, expired or belongs to a user without access. Check TEST_API_TOKEN with a single curl against a protected endpoint before blaming the handlers.

Where to go next after your first Codex-built API slice

Section titled “Where to go next after your first Codex-built API slice”