The AI-Ready Product Spec Template
A copy-paste product spec template designed to work with AI coding tools like Cursor, Claude Code, and Copilot.
AI coding tools are remarkably capable at generating code, but they're only as good as the instructions they receive. Feed Claude Code or Cursor a vague requirement like "build a task management feature" and you'll get something generic. Feed it a structured spec with data models, endpoint definitions, and concrete acceptance criteria, and you'll get something you can actually ship.
The problem is that traditional PRDs weren't designed for this. They were written for humans to read in meetings. They're full of narrative context, strategic justification, and high-level descriptions that make perfect sense to a product manager but give an AI tool almost nothing to work with.
This article provides a product spec template that's designed specifically to work well with AI coding tools. You can copy it, fill it in, and hand it directly to your AI assistant as context.
Why Traditional PRDs Fall Short
A typical PRD might describe a feature like this:
"Users should be able to manage their tasks efficiently. The system should support creating, editing, and deleting tasks. Tasks should have priorities and due dates. The experience should be intuitive and fast."
A human can infer a lot from this. An AI tool cannot. It doesn't know what "manage" means in terms of actual operations. It doesn't know the data types for priorities. It doesn't know what "intuitive" looks like in your design system.
AI tools need three things that traditional PRDs usually lack:
- Structure. Clear sections that separate data from behavior from UI. Not flowing prose.
- Specificity. Field names, types, enum values, HTTP methods, status codes. Not adjectives.
- Examples. Sample request/response payloads, expected UI states, edge cases spelled out. Not implications.
What Makes a Spec "AI-Ready"
An AI-ready spec is one where every section reduces ambiguity. Each part should answer questions the AI tool will have when generating code:
- What are the exact fields and their types?
- What operations are supported?
- What does a successful request look like?
- What does an error response look like?
- What should the UI show in each state?
The template below is organized around these questions. It's not exhaustive for every project, but it covers the sections that have the highest impact on code generation quality.
The Template
Copy the following and fill in the sections for your feature. Each section includes guidance on what to include.
# Feature: [Feature Name]
## Overview
A 2-3 sentence description of what this feature does and who it's for.
Do not include strategic justification or business metrics here.
Focus on what the user can do that they couldn't do before.
## User Stories
### [Story 1: Short title]
**As a** [user type],
**I want to** [specific action],
**So that** [concrete outcome].
**Acceptance Criteria:**
- [ ] [Specific, testable condition]
- [ ] [Another specific condition]
- [ ] [Edge case handling]
### [Story 2: Short title]
**As a** [user type],
**I want to** [specific action],
**So that** [concrete outcome].
**Acceptance Criteria:**
- [ ] [Specific, testable condition]
- [ ] [Another specific condition]
## Data Models
### [ModelName]
| Field | Type | Constraints | Description |
|-------|------|-------------|-------------|
| id | uuid | primary key, auto-generated | |
| [field] | [type] | [required/optional, unique, etc.] | [brief note] |
| [field] | [enum: value1, value2, value3] | [constraints] | [brief note] |
| created_at | timestamp | auto-generated | |
| updated_at | timestamp | auto-updated | |
**Relationships:**
- [ModelName] belongs to [OtherModel] via [foreign_key]
- [ModelName] has many [OtherModel]
### [ModelName2]
(Same structure as above)
## API Endpoints
### Create [Resource]
- **Method:** POST
- **Path:** `/api/v1/[resource]`
- **Auth:** Required
- **Request Body:**
```json
{
"title": "string (required, max 200 chars)",
"description": "string (optional)",
"priority": "enum: low | medium | high (default: medium)"
}
```
- **Success Response (201):**
```json
{
"id": "uuid",
"title": "string",
"description": "string | null",
"priority": "low | medium | high",
"created_at": "ISO 8601 timestamp"
}
```
- **Error Responses:**
- 400: Validation error (missing required fields, invalid enum value)
- 401: Not authenticated
- 403: Not authorized for this project
### List [Resources]
- **Method:** GET
- **Path:** `/api/v1/[resource]`
- **Auth:** Required
- **Query Parameters:**
- `page` (integer, default: 1)
- `per_page` (integer, default: 20, max: 100)
- `sort_by` (enum: created_at | updated_at | title, default: created_at)
- `sort_order` (enum: asc | desc, default: desc)
- `[filter_field]` (type, optional)
- **Success Response (200):**
```json
{
"items": [{ ... }],
"total": 42,
"page": 1,
"per_page": 20
}
```
### Get [Resource]
- **Method:** GET
- **Path:** `/api/v1/[resource]/{id}`
- **Auth:** Required
- **Success Response (200):** Single resource object
- **Error Responses:**
- 404: Resource not found
### Update [Resource]
- **Method:** PUT
- **Path:** `/api/v1/[resource]/{id}`
- **Auth:** Required
- **Request Body:** Same as Create (all fields optional)
- **Success Response (200):** Updated resource object
- **Error Responses:**
- 400: Validation error
- 404: Resource not found
### Delete [Resource]
- **Method:** DELETE
- **Path:** `/api/v1/[resource]/{id}`
- **Auth:** Required
- **Success Response (204):** No content
- **Error Responses:**
- 404: Resource not found
## UI Requirements
### [Page/Screen Name]
- **Route:** `/[path]`
- **Layout:** [Description of layout structure]
- **States:**
- **Empty state:** [What to show when no data exists]
- **Loading state:** [Skeleton, spinner, etc.]
- **Error state:** [What to show on API failure]
- **Populated state:** [Normal view with data]
### Components
- **[ComponentName]:** [What it does, key interactions]
- **[ComponentName]:** [What it does, key interactions]
### Interactions
- [Describe specific interactions: click, drag, keyboard shortcuts]
- [Describe transitions: what happens after form submission]
- [Describe validation: inline errors, when they appear]
## Edge Cases
- [What happens when [unusual condition]?]
- [What happens with [maximum/minimum values]?]
- [What happens during [concurrent operations]?]
- [What happens when [external service is unavailable]?]
Filling In the Template: A Worked Example
Let's walk through filling in key sections for a "Project Labels" feature, the kind of feature where users can create colored labels and apply them to items.
Overview Section
Keep it factual and scoped:
## Overview
Users can create, edit, and delete labels within a project. Labels have
a name and a color. Labels can be applied to tasks to categorize them.
Each task can have multiple labels. Labels are scoped to a project and
not shared across projects.
Notice what's not here: no mention of why labels are important, no market analysis, no KPIs. The AI doesn't need that. It needs to know what labels are and how they relate to other entities.
Data Models Section
Be explicit about types, constraints, and relationships:
### Label
| Field | Type | Constraints | Description |
|-------|------|-------------|-------------|
| id | uuid | primary key, auto-generated | |
| name | string | required, max 50 chars, unique per project | |
| color | string | required, hex format (#RRGGBB) | |
| project_id | uuid | required, foreign key | |
| created_at | timestamp | auto-generated | |
### TaskLabel (join table)
| Field | Type | Constraints | Description |
|-------|------|-------------|-------------|
| task_id | uuid | foreign key, part of composite PK | |
| label_id | uuid | foreign key, part of composite PK | |
| created_at | timestamp | auto-generated | When the label was applied |
The "unique per project" constraint on name is the kind of detail that makes a massive difference. Without it, the AI might generate code that allows duplicate label names, and you'll only catch it during QA.
API Endpoints Section
Include actual JSON payloads. Don't describe them, show them:
### Create Label
- **Method:** POST
- **Path:** `/api/v1/projects/{project_id}/labels`
- **Auth:** Required (project member)
- **Request Body:**
```json
{
"name": "Bug",
"color": "#FF0000"
}
- Success Response (201):
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Bug",
"color": "#FF0000",
"project_id": "proj-uuid-here",
"created_at": "2026-01-30T10:00:00Z"
}
- Error Responses:
- 400:
{"detail": "Label name already exists in this project"} - 400:
{"detail": "Invalid color format. Expected #RRGGBB"}
- 400:
Showing the actual error response format matters. If the AI knows your API returns `{"detail": "..."}`, it will generate frontend code that reads `error.detail` instead of guessing at `error.message` or `error.error`.
### Edge Cases Section
This is where most specs fall short, and where AI tools benefit the most from explicit guidance:
```markdown
## Edge Cases
- Deleting a label removes it from all tasks that have it applied
- Label names are case-insensitive for uniqueness ("Bug" and "bug" conflict)
- Maximum 50 labels per project
- Color validation accepts both uppercase and lowercase hex (#FF0000 and #ff0000)
- Applying the same label to a task twice is a no-op (returns 200, not an error)
Each of these translates directly into code the AI tool needs to write. Without them, it would have to guess, and guesses are where bugs come from.
Tips for Getting the Most Out of the Template
Write acceptance criteria as test cases
The best acceptance criteria read like test assertions. Instead of "user can create a task," write "submitting the form with a title creates a task and redirects to the task detail page." The more testable the criterion, the better code the AI will generate.
Include error response formats
This is the single most impactful detail to include. Frontend and backend mismatches on error formats are one of the most common sources of bugs. If your API returns {"detail": "Not found"} but the frontend expects {"message": "Not found"}, you'll get silent failures. Spell it out in the spec.
Use real field names
Don't say "the task should have a status." Say the field is called status, it's an enum with values todo, in_progress, and done, and the default is todo. The AI will use whatever names you give it. If you're vague, it'll make up names that might not match your conventions.
Specify what happens on success
It's not enough to say an endpoint returns the created resource. Show the response shape. Does it include nested objects? Does it include computed fields? What's the status code? AI tools generate much better frontend code when they know the exact shape of the response they're consuming.
Don't skip the UI states
Empty states, loading states, and error states are where most generated UIs fall short. If you describe them in the spec, the AI will generate components that handle them. If you don't, you'll get a component that renders data and crashes on everything else.
Keep it updated
A spec template is only useful if it reflects the current state of the feature. When you make changes during implementation, update the spec. If you change a field name, change it in the spec. If you add an endpoint, add it to the spec. The template is a living document, not a one-time artifact.
Using the Template with AI Tools
Once you've filled in the template, you can use it with your AI coding tool in a few ways:
As direct context. Paste the spec into the conversation or attach it as a file. Then ask the AI to implement specific sections: "Implement the Label data model and migration based on the spec" or "Build the Create Label endpoint matching the spec."
As a reference document. Keep the spec in your repo (e.g., in a docs/specs/ directory) and reference it when asking for changes. This works well with tools that can read your codebase.
Section by section. Don't dump the entire spec and say "build this." Work through it section by section: data models first, then endpoints, then UI. This gives the AI tool manageable context and lets you verify each layer before moving on.
The template isn't magic. It's just a way to organize the information that AI tools actually need. The more specific and structured you make it, the less time you'll spend fixing the generated code afterward.