Building the API Example the Industry Deserves
For over a decade, every developer learning OpenAPI (formerly Swagger) has encountered the same example: the Petstore API. While it served its purpose as a teaching tool, the original Petstore has become outdated, failing to reflect modern API design practices and the full capabilities of OpenAPI 3.x specifications.
The biggest problem? The old Petstore doesn't even follow basic RESTful design principles. This is catastrophic because it teaches developers anti-patterns that they carry into production systems.
Here are the specific RESTful violations in the original Swagger Petstore:
β BAD (Old Petstore):
/pet/{id} β Singular (incorrect)
/store/inventory β Plural (inconsistent)
β
GOOD (Modern Petstore):
/pets/{id} β Always plural for collections
/orders/{id} β Consistent pattern
Problem: Inconsistent naming confuses developers and breaks conventions.
β BAD (Old Petstore):
GET /pet/findByStatus?status=available β Action verb in URL
GET /pet/findByTags?tags=tag1,tag2 β Action verb in URL
β
GOOD (Modern Petstore):
GET /pets?status=AVAILABLE β Resource-oriented
QUERY /pets/search β Complex queries use QUERY method
Problem: URLs should represent resources (nouns), not actions (verbs).
β BAD (Old Petstore):
POST /pet β Returns 200 OK (should be 201 Created)
DELETE /pet/{id} β Returns 200 with body (should be 204 No Content)
β
GOOD (Modern Petstore):
POST /pets β Returns 201 Created + Location header
DELETE /pets/{id} β Returns 204 No Content
Problem: Incorrect status codes confuse clients and break HTTP semantics.
β BAD (Old Petstore):
GET /user/login?username=john&password=secret123
β
GOOD (Modern Petstore):
POST /login
Content-Type: application/json
{
"username": "john",
"password": "secret123"
}
Problem: The old Petstore uses GET for login, which:
β BAD (Old Petstore):
GET /pets β Returns bare array
[
{"id": 1, "name": "Fluffy"},
{"id": 2, "name": "Buddy"}
]
β
GOOD (Modern Petstore):
GET /pets β Returns wrapped collection with metadata
{
"data": [...],
"pagination": {
"page": 1,
"limit": 20,
"totalItems": 45,
"totalPages": 3
},
"links": {
"self": "...",
"next": "..."
}
}
Problem: Bare arrays can't be extended with metadata, breaking forward compatibility.
Beyond RESTful violations, the old Petstore has:
X-RateLimit headersTeaching developers with the old Petstore is like teaching driving with a car that has the brake and gas pedals swapped. It actively harms the industry.
It was time for a complete rewrite.
We've built a completely reimagined Petstore API from the ground upβone that showcases current best practices and demonstrates the full power of OpenAPI 3.2. This isn't just an update; it's a comprehensive reference implementation designed to be the new industry standard.
Create a pet store API that:
This is the foundation everything else builds on. We follow RESTful principles rigorously:
# Authentication
POST /login β POST for state-changing operations
POST /logout β POST (not GET)
# Resource Operations
GET /pets β Safe, idempotent retrieval
POST /pets β Create new resource
GET /pets/{id} β Retrieve specific resource
PUT /pets/{id} β Full update (idempotent)
PATCH /pets/{id} β Partial update (idempotent)
DELETE /pets/{id} β Remove resource (idempotent)
Every method follows HTTP semantics:
We use the right status code for every situation:
# Success Codes
201 Created β POST success + Location header pointing to new resource
200 OK β GET/PUT/PATCH success
204 No Content β DELETE success (no response body)
# Client Error Codes
400 Bad Request β Malformed request (invalid JSON, wrong content-type)
401 Unauthorized β Missing or invalid authentication
403 Forbidden β Authenticated but lacks permission
404 Not Found β Resource doesn't exist
409 Conflict β Resource conflict (duplicate creation)
422 Unprocessable β Valid request but validation failed
# Server Error Codes
500 Internal Error β Server-side failure
503 Service Unavailable β Temporary service disruption
Example response with proper status code:
HTTP/1.1 201 Created
Location: https://api.petstoreapi.com/v1/pets/pet_d0g_8n2q4w
Content-Type: application/json
{
"id": "pet_d0g_8n2q4w",
"species": "DOG",
"name": "Buddy",
...
}
URLs represent resources (nouns), not actions (verbs):
β BAD:
/getPets
/createPet
/updatePet
/deletePet
/findPetsByStatus
β
GOOD:
GET /pets
POST /pets
PUT /pets/{id}
DELETE /pets/{id}
GET /pets?status=AVAILABLE
For complex operations that don't fit CRUD, we use:
POST /orders/{id}/paymentGET /pets?status=AVAILABLE&species=DOGQUERY /pets/search with request bodyβ
Always use plural:
/pets β Collection
/pets/{id} β Single resource from collection
/users β Collection
/users/{id} β Single resource
/orders β Collection
/orders/{id} β Single resource
This consistency makes APIs predictable and intuitive.
β
Root resources use {id}:
/pets/{id}
/users/{id}
/orders/{id}
β
Nested resources use {parentId} for parent, {id} for child:
/users/{userId}/pets/{id}
/orders/{orderId}/items/{id}
Why this matters: Clients can predict URL patterns without reading docs.
Every collection endpoint returns a consistent structure:
{
"data": [
{ "id": "pet_c4t_5x7k9m", "name": "Whiskers", ... },
{ "id": "pet_d0g_8n2q4w", "name": "Buddy", ... }
],
"pagination": {
"page": 1,
"limit": 20,
"totalItems": 145,
"totalPages": 8
},
"links": {
"self": "https://api.petstoreapi.com/v1/pets?page=1",
"next": "https://api.petstoreapi.com/v1/pets?page=2",
"prev": null,
"first": "https://api.petstoreapi.com/v1/pets?page=1",
"last": "https://api.petstoreapi.com/v1/pets?page=8"
}
}
Benefits:
totalItems later without breaking clientsEvery resource includes navigational links:
{
"id": "pet_c4t_5x7k9m",
"name": "Whiskers",
"species": "CAT",
"status": "AVAILABLE",
"links": {
"self": "https://api.petstoreapi.com/v1/pets/pet_c4t_5x7k9m",
"adopt": "https://api.petstoreapi.com/v1/adoptions",
"images": "https://api.petstoreapi.com/v1/pets/pet_c4t_5x7k9m/images"
}
}
Clients can:
Support multiple representations:
GET /pets/{id}
Accept: application/json β Returns JSON
Accept: application/xml β Returns XML (if supported)
We standardize on JSON but the pattern supports future formats.
Operations that should be idempotent are:
β
Idempotent (same result when called multiple times):
GET /pets/{id} β Always returns same pet
PUT /pets/{id} β Update to same state produces same result
DELETE /pets/{id} β Deleting twice same as deleting once
PATCH /pets/{id} β Partial update to same fields is idempotent
β Not idempotent (different result each time):
POST /pets β Creates new pet with new ID each time
POST /orders β Creates new order each time
This allows safe retries on network failures.
We include proper cache headers:
HTTP/1.1 200 OK
Cache-Control: public, max-age=300
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Tue, 15 Nov 2025 12:45:26 GMT
# Conditional requests
GET /pets/{id}
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
HTTP/1.1 304 Not Modified
This reduces bandwidth and improves performance.
Beyond RESTful fundamentals, we leverage the latest OpenAPI 3.2 specification features:
Organize your API endpoints with structured tagging:
tags:
- name: Store
summary: Store operations
kind: category
- name: Pet
summary: Pet management
parent: Store # Nested under Store
kind: resource
This creates a clean hierarchy in documentation tools, making large APIs easier to navigate.
For complex searches that exceed URL length limits:
// Traditional GET has URL length limits
GET /pets?species=dog&age_min=12&age_max=36&good_with_kids=true...
// QUERY method uses request body while maintaining safe, idempotent semantics
QUERY /pets/search
{
"criteria": {
"species": ["DOG", "CAT"],
"ageRange": { "min": 12, "max": 36 },
"compatibility": { "goodWithKids": true }
},
"sort": { "field": "ageMonths", "order": "ASC" }
}
Perfect for smart TVs, IoT devices, and kiosks:
securitySchemes:
oauth2:
flows:
deviceCode:
deviceAuthorizationUrl: https://auth.petstoreapi.com/device/authorize
tokenUrl: https://auth.petstoreapi.com/token
scopes:
read:pets: View pet information
write:pets: Manage pets
DRY principle for consistent resource patterns:
components:
pathItems:
PetResource:
get:
summary: Get Pet
put:
summary: Update Pet
delete:
summary: Delete Pet
paths:
/pets/{id}:
$ref: '#/components/pathItems/PetResource'
We don't just follow OpenAPIβwe implement current web standards across the board:
No more generic error messages. Every error response follows the standard:
{
"type": "https://petstoreapi.com/errors/validation-error",
"title": "Validation Error",
"status": 422,
"detail": "The request body contains validation errors",
"instance": "/v1/pets",
"errors": [
{
"field": "ageMonths",
"message": "Must be a positive number",
"code": "INVALID_FORMAT"
}
]
}
Notice how it provides:
We use the modern standard, not legacy X-RateLimit headers:
RateLimit-Limit: 100
RateLimit-Remaining: 95
RateLimit-Reset: 1640000000
US, GB, CA)USD, EUR, GBP)2025-12-17T08:00:00Z)This ensures international compatibility and eliminates ambiguity.
Modern APIs aren't simple CRUD operations. We demonstrate complex, production-ready patterns:
Support multiple payment methods with discriminators:
{
"amount": 150.00,
"currency": "USD",
"source": {
"object": "CARD",
"name": "Jane Doe",
"number": "4242424242424242",
"expMonth": 12,
"expYear": 2025,
"cvc": "123"
}
}
// Or pay with bank account
{
"amount": 150.00,
"currency": "USD",
"source": {
"object": "BANK_ACCOUNT",
"accountHolderName": "Jane Doe",
"routingNumber": "110000000",
"accountNumber": "000123456789",
"accountType": "CHECKING"
}
}
The OpenAPI spec uses discriminators to validate the correct fields based on object type:
discriminator:
propertyName: object
mapping:
CARD: '#/components/schemas/CardPaymentSource'
BANK_ACCOUNT: '#/components/schemas/BankAccountPaymentSource'
Never return bare arrays. Every collection response includes:
{
"data": [
{
"id": "pet_c4t_5x7k9m",
"species": "CAT",
"name": "Whiskers",
"links": {
"self": "https://api.petstoreapi.com/v1/pets/pet_c4t_5x7k9m",
"adopt": "https://api.petstoreapi.com/v1/adoptions"
}
}
],
"pagination": {
"page": 1,
"limit": 20,
"totalItems": 45,
"totalPages": 3
},
"links": {
"self": "https://api.petstoreapi.com/v1/pets?page=1",
"next": "https://api.petstoreapi.com/v1/pets?page=2",
"last": "https://api.petstoreapi.com/v1/pets?page=3"
}
}
Benefits:
OpenAPI 3.x supports webhook definitions. We demonstrate real-world events:
webhooks:
petAdopted:
post:
summary: Pet Adopted Event
requestBody:
content:
application/json:
schema:
type: object
properties:
eventId:
type: string
format: uuid
eventType:
type: string
const: pet.adopted
timestamp:
type: string
format: date-time
data:
type: object
properties:
pet:
$ref: '#/components/schemas/Pet'
adopter:
$ref: '#/components/schemas/User'
Your application receives events when important actions occurβno polling needed.
AI features need streaming responses. We demonstrate with a Pet Adoption Advisor:
const response = await fetch('https://api.petstoreapi.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
},
body: JSON.stringify({
messages: [
{ role: 'USER', content: 'What should I know before adopting a cat?' }
],
model: 'PET_ADVISOR_1',
stream: true
})
});
// Stream tokens as they arrive
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter(line => line.trim());
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
process.stdout.write(data.choices[0]?.delta?.content || '');
}
}
}
The OpenAPI spec documents the SSE format with text/event-stream media type.
Great APIs are easy to use. We've focused heavily on developer experience:
Every major operation includes x-codeSamples with ready-to-use examples:
TypeScript:
import { PetStoreAPI } from '@petstoreapi/sdk';
const client = new PetStoreAPI({
apiKey: process.env.PETSTORE_API_KEY
});
const pet = await client.pets.get('pet_c4t_5x7k9m');
console.log(`Found ${pet.name}, a ${pet.ageMonths}-month-old ${pet.species}`);
Python:
from petstore import PetStoreAPI
client = PetStoreAPI(api_key=os.environ['PETSTORE_API_KEY'])
pet = client.pets.get('pet_c4t_5x7k9m')
print(f"Found {pet.name}, a {pet.age_months}-month-old {pet.species}")
cURL:
curl https://api.petstoreapi.com/v1/pets/pet_c4t_5x7k9m \
-H "Accept: application/json"
Every operation includes business context, not just technical details:
description: |
Add a new pet to the store catalog, making it available for adoption.
## Pet Lifecycle Workflow
When a new pet enters the system:
1. **Intake**: Staff creates a pet record with this endpoint (status: `AVAILABLE`)
2. **Profile**: Pet details include species, breed, age, medical info, photos
3. **Discovery**: Pet appears in search results and listings
4. **Adoption Application**: Potential adopters can apply
5. **Adoption**: Once approved, pet status changes to `ADOPTED`
**Access**: Requires staff permissions (`write:pets` scope or Bearer token).
Complex features link to detailed guides:
externalDocs:
description: Learn more about webhooks
url: https://petstoreapi.com/docs/webhooks
We've chosen a modern, performant stack:
This isn't a toy exampleβit's production infrastructure.
// Types generated from OpenAPI schema
interface Pet {
id: string;
species: 'DOG' | 'CAT' | 'RABBIT' | 'BIRD' | 'REPTILE' | 'OTHER';
name: string;
ageMonths: number;
status: 'AVAILABLE' | 'PENDING' | 'ADOPTED' | 'NOT_AVAILABLE';
// ... more fields
}
// Request handlers are fully typed
export async function getPet(c: Context): Promise<Pet> {
const petId = c.req.param('id');
const pet = await petStore.get(petId);
if (!pet) {
throw errors.notFound('Pet not found');
}
return pet;
}
We use unevaluatedProperties: false to catch unexpected fields:
Pet:
type: object
required: [id, name, species, ageMonths, status]
properties:
id: { type: string }
name: { type: string }
# ...
unevaluatedProperties: false # Reject unknown fields
This prevents clients from sending invalid data and makes API evolution explicit.
Let's see how we stack up against the original Swagger Petstore and other modern examples. RESTful compliance is listed first as it's the foundation:
| Feature | Old Petstore | Train Travel API | Modern Petstore |
|---|---|---|---|
| π΄ RESTful: HTTP Methods | β GET for login/logout | β POST for mutations | β Correct methods everywhere |
| π΄ RESTful: Status Codes | β 200 for POST/DELETE | β Proper codes | β 201/204/4xx/5xx properly |
| π΄ RESTful: URL Design | β /findByStatus (verbs) | β Resource-oriented | β Pure resource nouns |
| π΄ RESTful: Plural Resources | β /pet (singular) | β /trips (plural) | β /pets, /users, /orders |
| π΄ RESTful: Path Parameters | β /user/{username} mixed | β Consistent | β {id} uniformly |
| π΄ RESTful: Collections | β Bare arrays | β Wrapped | β Wrapped + pagination + links |
| π΄ RESTful: HATEOAS | β None | β οΈ Basic links | β Full hypermedia |
| π΄ RESTful: Idempotency | β Not documented | β Documented | β Explicit + safe retries |
| OpenAPI Version | 2.0 | 3.1.0 | 3.2.0 β |
| Error Standard | Custom | RFC 9457 | RFC 9457 + Field Details β |
| Rate Limiting | X-RateLimit | RateLimit-* | RateLimit-* β |
| Auth Methods | API Key in URL | OAuth | OAuth + Bearer/JWT β |
| OAuth Flows | None | Authorization Code | Auth Code + Device Flow β |
| Webhooks | None | Basic | Complete with Security β |
| Real-time (SSE) | None | None | Full SSE Implementation β |
| QUERY Method | None | None | Yes (OpenAPI 3.2) β |
| Hierarchical Tags | None | Flat | Full Hierarchy β |
| Code Samples | None | Basic | Multi-language x-codeSamples β |
| Polymorphic Types | None | Basic | Discriminators with Mapping β |
| Production Ready | No | Partial | Fully Deployable β |
Result: We lead in every category, with RESTful compliance being our strongest differentiator.
The old Petstore violates 8 out of 8 core RESTful principles. This isn't just outdatedβit's teaching harmful patterns. Every developer who learned from it has to unlearn those anti-patterns.
The Modern Petstore follows 100% of RESTful principles plus modern extensions like QUERY method and HATEOAS.
This isn't just an exampleβit's a template for building production APIs:
The obvious use case. Organizations can fork this and customize:
The patterns translate perfectly:
Same structural patterns:
Libraries, equipment rentals, room bookings:
# Clone the repository
git clone https://github.com/your-org/petstoreapi.com.git
cd petstoreapi.com/packages/api
# Install dependencies
npm install
# Start local development
npm run dev
# API available at http://localhost:8787
# Seed sample data
curl -X POST http://localhost:8787/v1/seed
# List pets
curl http://localhost:8787/v1/pets
# Get specific pet
curl http://localhost:8787/v1/pets/pet_cat_5x7k9m
# Try AI chat
curl -X POST http://localhost:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "USER", "content": "Tell me about Golden Retrievers"}],
"stream": false
}'
Study our OpenAPI spec to learn:
readOnly vs writeOnlyLearn implementation patterns:
See how to document:
These principles guided every decision:
We prefer established standards (RFC 9457, ISO codes, IETF headers) over inventing our own. Standards are:
Examples that are "too simple" teach bad habits. We include:
We model actual business logic:
We target the latest specifications:
If you need older versions, this shows what you're missing.
Every feature is fully implemented:
No "TODO" comments or stub implementations.
We're continuously improving the Modern Petstore API:
This project thrives on community contributions:
The Modern Pet Store API isn't just an updateβit's a reimagining of what an API example should be:
β
Complete OpenAPI 3.2 showcase
β
Current web standards throughout
β
Production-ready architecture
β
Real-world business patterns
β
Comprehensive documentation
β
Multi-language code samples
β
Deployable to Cloudflare Edge
Whether you're learning OpenAPI, designing a new API, evaluating API tools, or teaching others, the Modern Pet Store API provides a comprehensive, realistic reference.
The classic Petstore served us well for over a decade. Now it's time for a new standard.
Live API: https://api.petstoreapi.com
Interactive Docs: https://api.petstoreapi.com/v1/docs
GitHub: https://github.com/your-org/petstoreapi.com
OpenAPI Spec: https://api.petstoreapi.com/v1/openapi.json
Give it a star β if you find it useful!
The Modern Pet Store API is an open-source project created to demonstrate best practices in API design. It's maintained by the community and welcomes contributions from developers worldwide.
Built with β€οΈ using:
License: MIT
Last updated: December 2025