# How to build an API with AI: a REST backend step by step

URL: https://agentvera.dev/en/blog/build-api-with-ai/ · Published: 2026-10-25

A practical guide to build an API with AI: stack choice, schema and migrations, auth, validation, tests, OpenAPI docs, deploy and the security mistakes agents make.

**In short:**

- Write the resources, endpoints, error format and rules into CLAUDE.md or AGENTS.md first, then have the agent build one resource at a time with tests and a commit after each step.
- Validate every request body with a schema, change the database only through migrations, and check on the server that each record belongs to the caller, not just that the caller is logged in.
- Generate OpenAPI docs from the same schemas, run migrations as a separate deploy step, and have a developer review auth, authorization and data handling before real users arrive.

You can build an API with AI in an afternoon: ask a [coding agent](/en/glossary/coding-agent/) like Claude Code or Codex for “a REST API for tasks with users” and you’ll get routes, a database and a running server. Whether that API survives its first real users is another question. Unvalidated input, endpoints that hand out other people’s data and migrations nobody read are the usual problems. This post walks through how I build a REST backend with agents, from stack choice to deploy, and where to slow down.

It’s part of the series that started with [building a SaaS with AI](/en/blog/build-saas-mvp-with-ai/). The SaaS post covers the product; this one covers the backend underneath it, for a mobile app, a frontend or other services to call. If you’re new to working this way, [how to vibe code](/en/blog/how-to-vibe-code/) is a good warm-up.

## 1. Define the API before any code

An API is a contract. Once a mobile app ships against it, changing a field name is expensive. So before the agent writes code, write down:

- **Resources:** the nouns. For example `users`, `projects`, `tasks`, `comments`.
- **Endpoints:** `GET /v1/projects`, `POST /v1/projects`, `GET /v1/projects/:id/tasks`, `PATCH /v1/tasks/:id` and so on.
- **Who can do what:** “Members of a project can read its tasks; only the owner can delete the project.”
- **Error format:** one shape for every error, with an HTTP status, a machine-readable code and a message.
- **Pagination:** cursor or page, default and maximum page size.

Keep the first version to the endpoints a real client needs. An admin API, webhooks and bulk import can wait.

## 2. Put the rules in a spec file

Agents don’t remember earlier conversations. Claude Code reads `CLAUDE.md` at the start of every session; Codex and many other agents read `AGENTS.md`. Put your decisions there:

```markdown
# Tasks API

## Stack
- Node.js LTS, TypeScript, Fastify
- PostgreSQL, Prisma for schema and migrations
- Zod for request validation; OpenAPI generated from the same schemas
- Vitest; tests use app.inject() against a test database

## Rules
- All routes under /v1. JSON only.
- Every request body, query and param is validated with a schema.
- Errors: { "error": { "code": "...", "message": "..." } }, never a stack trace.
- Every query on tasks and projects checks project membership.
- Never return password hashes or tokens; use explicit response schemas.
- Database changes only through migration files.
- List endpoints: cursor pagination, default 20, max 100.
- Secrets come from environment variables; never in code or logs.
- Ask before adding a dependency.
- After every task: pnpm lint && pnpm test

## Out of scope (for now)
Admin API, file uploads, webhooks, GraphQL.
```

Each rule here exists because agents get it wrong without it. “Explicit response schemas” stops the agent from returning the whole user row, password hash included. “Max 100” stops someone from asking for a million rows. More on writing this file in [CLAUDE.md and AGENTS.md](/en/blog/claude-md-agents-md/).

## 3. Pick a boring stack

The agent writes better code on a stack it has seen thousands of times. Two safe choices:

| | TypeScript | Python |
| --- | --- | --- |
| Framework | Fastify (or Express, Hono) | FastAPI |
| Validation | Zod or JSON Schema | Pydantic |
| Database access | Prisma or Drizzle | SQLAlchemy |
| Migrations | Prisma Migrate, Drizzle Kit | Alembic |
| OpenAPI | @fastify/swagger | Built in |
| Tests | Vitest + `app.inject()` | pytest + `TestClient` |

Both work. Pick the language you can read, because you will be reading a lot of diffs. I’ll use TypeScript, Fastify, Prisma and PostgreSQL in the examples.

Run Postgres locally with Docker so the agent never needs a shared database:

```bash
docker run --name tasks-db -e POSTGRES_PASSWORD=dev -p 5432:5432 -d postgres:16
mkdir tasks-api && cd tasks-api && git init
```

## 4. Plan first, then one resource at a time

My first prompt asks for a plan, not code:

```text
Read CLAUDE.md. Don't write code yet.
Propose:
1. The Prisma schema (models, relations, indexes, unique constraints)
2. The full endpoint list with request and response shapes
3. How authorization works for each endpoint
4. 6–8 implementation steps, each ending with tests that pass
List open questions at the end.
```

Fix the plan, answer the questions, update the spec. Then go step by step:

```text
Implement step 2: the projects resource (list, create, get, update,
delete). Validate input with Zod, use explicit response schemas,
check membership on every query. Write tests for the happy path,
invalid input, and a second user who is not a member. Run the tests,
show me the result, then stop.
```

“Then stop” is there so I can read the diff and commit before the next step. When the agent drifts, I go back one commit instead of untangling five resources.

## 5. Schema and migrations

The schema is the hardest part to change later, so read it carefully:

- **Constraints in the database, not only in code.** `NOT NULL`, unique emails, foreign keys with a deliberate `ON DELETE` behavior.
- **Indexes for the queries you run.** Every foreign key you filter on and every column you sort by in a list endpoint.
- **Timestamps** (`createdAt`, `updatedAt`) on every table; they help with debugging and cursor pagination.
- **Money as integers or decimals**, never floating point.

Every change goes through a migration:

```bash
npx prisma migrate dev --name add_tasks   # create and apply locally
npx prisma migrate reset                  # rebuild the dev database from zero
npx prisma migrate deploy                 # apply pending migrations in production
```

Once there’s real data, read every migration file the agent writes. Stop and ask about `DROP COLUMN`, `DROP TABLE`, renames (Prisma can turn them into drop and add), `UPDATE` or `DELETE` without `WHERE`, and new `NOT NULL` columns on big tables without a default.

## 6. Authentication: use something that exists

Don’t have the agent invent password storage or token formats. Use a hosted auth provider or a well-known library, and keep the agent’s job to wiring it in:

- **Passwords** hashed with argon2 or bcrypt through a maintained library, never stored or logged in plain text.
- **Sessions or tokens:** for a browser client on the same site, an `HttpOnly`, `Secure`, `SameSite` session cookie is the simplest safe choice. For mobile or third-party clients, short-lived access tokens with refresh tokens.
- **Verify tokens properly.** Signature, expiry, issuer and audience; reject the `none` algorithm. Ask the agent to show you exactly where this happens.
- **Rate-limit login and password reset** so nobody can try thousands of passwords.

## 7. Authorization: the bug agents write most

Authentication answers “who is this?” Authorization answers “may they touch this record?” Agent-written APIs usually get the first right and the second wrong. The classic version is `GET /v1/tasks/:id` that checks a valid token, then loads the task by ID. Any logged-in user can read any task by changing the number. OWASP lists this, broken object level authorization, first in its API Security Top 10.

The fix is a habit, not a library:

- Every query that loads a record by ID also filters by what the caller may access: `where: { id, project: { members: { some: { userId } } } }`.
- Not found and not yours return the same `404`, so IDs can’t be probed.
- **Test it with two users** on every endpoint that takes an ID. These tests are the most valuable ones in the project.

A related mistake is mass assignment: the agent passes the request body straight into `update()`, and a user sends `"role": "admin"` or `"ownerId": "..."`. Validation schemas that list only the allowed fields stop this.

Ask the agent: “List every endpoint and the authorization check it performs. Mark any endpoint that loads a record by ID without filtering by the caller.”

## 8. Validation and errors

Every input crossing the boundary is validated: body, query string, path params, headers you use. With Zod and Fastify the route declares its schemas, and invalid requests are rejected with a `400` before your code runs.

- **Reject unknown fields** instead of silently ignoring them; it surfaces client bugs early.
- **Limit sizes:** string lengths, array lengths, request body size, page size.
- **One error shape** for everything, and a global error handler that logs the details server-side but returns only `code` and `message` to the client. Stack traces and SQL errors in responses give attackers a map.
- **Parameterized queries only.** The ORM does this by default; watch for raw SQL helpers with string concatenation, such as Prisma’s `$queryRawUnsafe`.

## 9. Tests that matter

Fastify’s `app.inject()` sends requests to the app without opening a port, so API tests are fast. You don’t need 100% coverage; you need these:

1. **Each endpoint’s happy path**, checking status code and response shape.
2. **Invalid input** returns `400` with your error format.
3. **No token** returns `401`; **someone else’s record** returns `404`.
4. **Pagination** honors the maximum.
5. **Migrations apply from zero** on a clean database.

Tests run against a separate test database that is reset between runs. Never let the test suite near production credentials.

Watch for the agent “fixing” a failing test by changing the assertion. If a test file shows up in a diff you didn’t ask for, ask why. [AI code review](/en/blog/ai-code-review/) has a checklist for this.

## 10. OpenAPI docs from the same schemas

Docs that are written by hand drift. Generate them from the validation schemas so the documentation describes exactly what the code accepts. In Fastify, `@fastify/swagger` builds an OpenAPI document from your route schemas and `@fastify/swagger-ui` serves it; FastAPI does this out of the box.

```text
Add @fastify/swagger and @fastify/swagger-ui. Generate the OpenAPI
document from the existing route schemas, add descriptions and
examples, document the error format and auth. Serve the UI at /docs
only when NODE_ENV is not production.
```

The OpenAPI file is useful beyond docs: frontend agents can generate a typed client from it, and other tools can discover your endpoints from it.

## 11. Deploy

A small API runs fine as a container on a platform like Fly.io, Railway or Render, or on any server with Docker. A checklist:

- **A Dockerfile** with a multi-stage build and a non-root user. Ask the agent for one, then read it.
- **Migrations as a separate step** (`prisma migrate deploy`) before the new version starts, not inside every app instance on boot.
- **Environment variables** for the database URL, secrets and allowed CORS origins, set in the platform, not in the image.
- **A health endpoint** (`/health`) the platform can check.
- **CORS** limited to your real frontends; never `*` with credentials.
- **HTTPS only**, security headers (for example with `@fastify/helmet`), and request logging without passwords, tokens or personal data.
- **Backups** turned on for the production database, and a restore tried at least once.
- **Error tracking** such as Sentry, so you hear about the `500`s before your users tell you.

## What the AI gets wrong

- **Returning whole database rows.** Password hashes, internal flags and other users’ emails in the JSON. Explicit response schemas fix it.
- **Inventing APIs.** Library functions that don’t exist or changed versions ago. Give the agent current docs, and paste errors back as they are.
- **No pagination.** `findMany()` with no limit works until the table has 200,000 rows.
- **N+1 queries.** A query per item inside a loop. Ask the agent to log queries in development and look for repeats.
- **Swallowed errors.** `catch {}` that returns `200` anyway.
- **Secrets in code.** A hard-coded JWT secret “just for now” that ships to production.
- **Scope creep.** “I also added an admin endpoint.” Revert it or move it to its own branch.

## Security: short but serious

- Keep `.env` in `.gitignore`; rotate any key that was ever committed.
- Don’t paste API keys into the chat; the agent only needs the variable name.
- Point the agent at a local or staging database, never production.
- Read commands the agent runs, especially database commands, `rm`, force pushes and `curl … | sh`.
- Run `pnpm audit` (or your package manager’s equivalent) now and then and hand the output to the agent.
- Rate-limit public endpoints, not just login.

For more on keeping agents safe on your machine, see [AI coding agent security](/en/blog/ai-coding-agent-security/).

## When to bring in a human developer

A few hours of review by an experienced backend developer pays for itself:

- Before real users sign up, for authentication and authorization
- When the API stores personal, health or payment data
- When a partner or enterprise customer will integrate against it
- When performance falls over: slow queries, missing indexes, connection limits
- When you and the agent have failed to fix the same bug three times

Make it easy for them: the spec file, the OpenAPI document, the migrations folder and a green test run.

## How I do this with AgentVera

All of this works in one terminal. With several agents, AgentVera helps in these places:

- **Parallel work in worktrees.** In the [workspace](/en/features/workspace/) I run one agent on the API and another on the frontend or the tests, each in its own [git worktree](/en/glossary/git-worktree/) so they don’t step on each other’s files. The method is in [git worktrees for parallel agents](/en/blog/git-worktree-parallel-agents/).
- **Trying endpoints without another tool.** The [API client](/en/features/api-client/) finds the project’s endpoints from OpenAPI files and from routes in Express, Fastify, Hono, Next.js, FastAPI, Django, NestJS and others. I send a request with parameters, headers and a body, and if the response is an error, I hand the request and response to an agent in one click.
- **Seeing what landed in the database.** The [database manager](/en/features/database-manager/) opens local Postgres so I can check the rows the agent created, and [DB Guard](/en/features/db-guard/) rates the risk of each migration when the agent ends its turn and can try it on a shadow database.
- **Containers.** The [Docker page](/en/features/docker/) shows the Postgres container and its logs next to the agents.
- **Reviewing turns.** [Code review](/en/features/code-review/) checks every finished turn before I merge.

These speed up the loop; the spec, the tests and your own review are still what make the API safe.

## Wrapping up

To build an API with AI, decide the contract first, write the rules into `CLAUDE.md` or `AGENTS.md`, pick a mainstream stack, and have the agent build one resource at a time with tests. Validate every input, change the database only through migrations, check ownership on every record, generate OpenAPI from the same schemas, and run migrations as a separate deploy step. Get a human review before real users arrive. If you want to run the backend, frontend and test agents side by side, [download AgentVera for free](/en/download/).

## Frequently asked questions

**Can I build a REST API with AI if I’m not a backend developer?** You can build a working API for a small product with a coding agent, as long as you keep the scope small, write the rules down for the agent and test each endpoint. Authentication, authorization and anything touching personal or payment data should be reviewed by an experienced developer before launch.

**Which stack is best for building an API with AI?** A mainstream one the agents have seen often: TypeScript with Fastify or Express, or Python with FastAPI, plus PostgreSQL and a migration tool. Popular stacks mean fewer invented functions and more help when you get stuck.

**What is the most common security bug in AI-written APIs?** Missing object-level authorization: the endpoint checks that the user is logged in but not that the record belongs to them, so changing an ID in the URL returns someone else’s data. Test it with two users on every endpoint that takes an ID.

**Should the AI write my authentication from scratch?** No. Use a well-known library or a hosted auth provider for passwords, sessions and tokens. Let the agent wire it up and write the authorization rules, which are specific to your app, and test those rules carefully.

**How do I get OpenAPI docs for an AI-built API?** Have the agent generate them from the same schemas that validate requests, for example with @fastify/swagger in Fastify or the built-in docs in FastAPI. Then the docs can’t drift from what the code actually accepts.

---

**About AgentVera.** AgentVera is a desktop app for macOS, Windows and Linux, built by Solviera Teknoloji. It runs 18 AI coding agents (Claude Code, Codex, Gemini CLI, Copilot, Cursor, Antigravity, opencode, Hermes, Qwen Code, Kimi Code, Kiro, Amp, Droid, Goose, Vibe, Crush, Aider, Cline) side by side, per project, in real terminals with the user's own CLI installs and logins. The free plan has unlimited agents. Download: https://agentvera.dev/en/download/ · Pricing: https://agentvera.dev/en/pricing/
