Requirements to Technical Specs
Product asks arrive as user stories; engineers ship technical specs - API shapes, data models, failure modes, and rollout plans. Good translation prevents "build the button" without knowing billing rules, idempotency, or Python service boundaries.
Recipe
Quick-reference recipe card - copy-paste ready.
# Tech spec: <feature>
## Problem / user outcome
## Scope (in) / Non-goals (out)
## API sketch (OpenAPI paths or events)
## Data model changes (tables, migrations expand-contract)
## Failure modes & retries
## Observability (logs, metrics, alerts)
## Rollout (flag, canary) & rollback
## Acceptance tests (Given/When/Then)
## Open questions & spikesWhen to reach for this:
- Story points exceed one sprint
- Cross-service or schema impact
- PM asks for date before engineering understands shape
- Compliance or security touch
Working Example
# Tech spec: Export invoices as PDF
## Problem
Finance users need PDF invoices in bulk export (up to 500/request).
## Non-goals
- Email delivery (separate epic)
- Custom branding per tenant (phase 2)
## API
`POST /v1/invoices/export` → 202 + `job_id`
`GET /v1/jobs/{id}` → status + S3 URL when ready
## Data
Read `invoices` + `line_items`; no schema change phase 1.
## Implementation (Python)
- FastAPI route enqueues Celery `export_invoices` task
- Worker uses ReportLab; uploads to S3 via boto3
- Idempotency-Key header dedupes retries
## Failure modes
- S3 down → retry with tenacity; DLQ after 5
- PDF > 50MB → fail job with clear error code
## Acceptance
- Given 10 invoices, When export job completes, Then ZIP URL valid 24h
- p95 job time < 60s for 100 invoices in staging load test# Sketch Pydantic models spec reviewers validate early
from pydantic import BaseModel
class ExportRequest(BaseModel):
invoice_ids: list[str]
max_count: int = 500
class JobStatus(BaseModel):
job_id: str
state: str # pending | running | done | failed
download_url: str | None = NoneWhat this demonstrates:
- User outcome stated without prescribing UI-only solutions
- Async job pattern chosen explicitly for Python stack
- Failure modes and acceptance criteria are testable
- OpenAPI-shaped models align PM and engineering early
Deep Dive
How It Works
- Discovery questions - Who, frequency, compliance, peak volume, failure tolerance.
- Vertical slice - Thin end-to-end path before polishing edge cases.
- Contract first - Pydantic models or OpenAPI before deep implementation.
- Migration discipline - Expand-contract called out in spec, not surprise PR.
- Review with PM - Walk acceptance tests; adjust scope before sprint commit.
Spec Quality Checklist
| Section | Missing = risk |
|---|---|
| Non-goals | Scope creep mid-sprint |
| Rollback | Incident without mitigation |
| Observability | Blind production launch |
| Idempotency | Duplicate charges/exports |
Python Notes
# Acceptance test stub from spec
def test_export_job_returns_download_url(client, invoice_factory):
ids = [invoice_factory().id for _ in range(3)]
r = client.post("/v1/invoices/export", json={"invoice_ids": ids})
assert r.status_code == 202Gotchas
- Spec after code started - Rework and blame. Fix: No sprint commit without reviewed spec for medium+ work.
- Vague acceptance - "Fast" or "scalable." Fix: Numbers: p95, max rows, error codes.
- Ignoring worker tier - API spec without queue story. Fix: Celery task section mandatory for async work.
- Hidden compliance - PII in PDF logs discovered late. Fix: Data classification in spec header.
- One giant spec - Never ships. Fix: Phase 1 non-goals explicit; phase 2 ticket stub.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| RFC for architecture | Cross-team debate | Simple CRUD story |
| Job story mapping session | New product area | Single API endpoint |
| Prototype spike | High uncertainty UX | Regulated billing without spec |
| BDD Gherkin | Business-readable acceptance | Internal-only tooling |
FAQs
How long should specs take?
Half day to two days for medium features; spike if longer than three days writing.
Who approves specs?
Tech lead + PM sign acceptance; security for PII/payment.
Spec for bug fixes?
Lightweight: repro, root cause hypothesis, test plan - skip full template.
Django vs FastAPI in spec?
State framework assumptions and shared auth patterns explicitly.
How handle changing requirements?
Amend spec version; re-estimate; do not silently absorb scope.
Specs and Jira?
Link spec PR or doc in epic; acceptance tests copied to ticket.
ML features in spec?
Include data sources, eval metric, rollback to previous model version.
When involve design?
Before API freeze when UX drives pagination, filters, or export limits.
Open questions limit?
If >3 blockers, time-box spikes before sprint commitment.
Spec storage?
docs/specs/ in repo or linked RFC folder - versioned with code.
Related
- Estimation & Risk - size after spec
- Roadmap Contributions - feed planning
- Running Design Reviews - large changes
- BDD & Gherkin - acceptance format
- Pydantic Models - contract modeling
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.