Skip to main content

Flask API Documentation

Introduction

Well-documented APIs are critical for both the developers who maintain them and the users who consume them. In this guide, we'll explore how to create clear, comprehensive documentation for your Flask REST APIs. Good documentation helps users understand your API's capabilities, reduces onboarding time, and minimizes implementation errors.

API documentation should explain:

  • Available endpoints and operations
  • Request parameters and response formats
  • Authentication requirements
  • Error codes and handling
  • Usage examples

Let's dive into how to create excellent documentation for your Flask APIs.

Why Document Your API?

Before we jump into the tools, let's understand why documentation matters:

  1. Improved Developer Experience: Clear docs help developers integrate with your API faster
  2. Reduced Support Requests: Comprehensive documentation answers common questions
  3. Better Testing: Documentation tools often provide interactive testing capabilities
  4. Consistency: Documentation enforces consistent API design and implementation
  5. Discoverability: Users can easily explore what your API offers

Manual Documentation Options

Creating Documentation with Markdown

For simple APIs, you might start with manual documentation using Markdown:

python
"""
# User API

## Get User
GET /api/users/<id>

Returns information about a specific user.

### Parameters
- id: The user ID (integer)

### Response
```json
{
"id": 1,
"name": "John Smith",
"email": "[email protected]"
}

Error Responses

  • 404: User not found
  • 500: Server error
python
@app.route('/api/users/<int:id>', methods=['GET'])
def get_user(id):
# Implementation here
pass

While manual documentation works for small projects, it becomes difficult to maintain for larger APIs. That's where automated tools come in.

Automated Documentation with Swagger/OpenAPI

What is Swagger/OpenAPI?

Swagger (now known as OpenAPI) is a specification for documenting REST APIs. It allows you to describe your entire API, including:

  • Available endpoints and operations
  • Operation parameters and authentication methods
  • Contact information, license, terms of use and other details

Using Flask-RESTPlus

Flask-RESTPlus integrates Swagger documentation with Flask and makes it easy to document your API.

First, install Flask-RESTPlus:

bash
pip install flask-restplus

Here's a basic example:

python
from flask import Flask
from flask_restplus import Api, Resource, fields

app = Flask(__name__)
api = Api(app, version='1.0', title='User API',
description='A simple User API',
)

ns = api.namespace('users', description='User operations')

user_model = api.model('User', {
'id': fields.Integer(readonly=True, description='The user unique identifier'),
'name': fields.String(required=True, description='User name'),
'email': fields.String(required=True, description='User email')
})

@ns.route('/<int:id>')
@ns.response(404, 'User not found')
@ns.param('id', 'The user identifier')
class User(Resource):
@ns.doc('get_user')
@ns.marshal_with(user_model)
def get(self, id):
"""Get a specific user by ID"""
# Implementation here
return {"id": id, "name": "Example User", "email": "[email protected]"}

if __name__ == '__main__':
app.run(debug=True)

When you run this application and navigate to the root URL (e.g., http://localhost:5000/), you'll see a Swagger UI page displaying your API documentation with interactive testing capabilities.

Using Flasgger

Flasgger is another popular option for integrating Swagger with Flask. It's particularly useful for documenting existing APIs.

First, install Flasgger:

bash
pip install flasgger

Here's a basic example:

python
from flask import Flask, jsonify
from flasgger import Swagger

app = Flask(__name__)
swagger = Swagger(app)

@app.route('/users/<int:id>', methods=['GET'])
def get_user(id):
"""Get User Information
---
parameters:
- name: id
in: path
type: integer
required: true
description: The user ID
responses:
200:
description: User found
schema:
id: User
properties:
id:
type: integer
description: The user ID
name:
type: string
description: The user name
email:
type: string
description: The user email
404:
description: User not found
"""
# Implementation here
return jsonify({"id": id, "name": "Example User", "email": "[email protected]"})

if __name__ == '__main__':
app.run(debug=True)

With Flasgger, you can document your API using docstrings in YAML format.

Flask-RESTX (Successor to Flask-RESTPlus)

Flask-RESTX is the actively maintained successor to Flask-RESTPlus and provides similar functionality:

bash
pip install flask-restx

Here's how to use it:

python
from flask import Flask
from flask_restx import Api, Resource, fields

app = Flask(__name__)
api = Api(
app,
version='1.0',
title='Todo API',
description='A simple TODO API',
doc='/docs'
)

ns = api.namespace('todos', description='TODO operations')

todo_model = api.model('Todo', {
'id': fields.Integer(readonly=True, description='The task unique identifier'),
'task': fields.String(required=True, description='The task details')
})

class TodoDAO(object):
def __init__(self):
self.todos = []
self.counter = 0

def get(self, id):
for todo in self.todos:
if todo['id'] == id:
return todo
api.abort(404, f"Todo {id} doesn't exist")

def create(self, data):
todo = data
todo['id'] = self.counter = self.counter + 1
self.todos.append(todo)
return todo

DAO = TodoDAO()

@ns.route('/')
class TodoList(Resource):
@ns.doc('list_todos')
@ns.marshal_list_with(todo_model)
def get(self):
"""List all tasks"""
return DAO.todos

@ns.doc('create_todo')
@ns.expect(todo_model)
@ns.marshal_with(todo_model, code=201)
def post(self):
"""Create a new task"""
return DAO.create(api.payload), 201

@ns.route('/<int:id>')
@ns.response(404, 'Todo not found')
@ns.param('id', 'The task identifier')
class Todo(Resource):
@ns.doc('get_todo')
@ns.marshal_with(todo_model)
def get(self, id):
"""Fetch a task given its identifier"""
return DAO.get(id)

if __name__ == '__main__':
app.run(debug=True)

Visit http://localhost:5000/docs to see your API documentation.

Best Practices for API Documentation

  1. Be Consistent: Use the same naming conventions and structure throughout your documentation

  2. Include Examples: Provide request and response examples for each endpoint

  3. Document Errors: Include possible error responses and their meanings

  4. Keep It Updated: Ensure documentation stays in sync with your API implementation

  5. Use Clear Descriptions: Write concise, clear descriptions for each endpoint, parameter, and field

  6. Group Related Endpoints: Organize endpoints into logical groups (like Users, Orders, etc.)

  7. Document Authentication: Clearly explain how to authenticate with your API

Practical Example: Building a Book API with Documentation

Let's create a more complete example for a book catalog API:

python
from flask import Flask
from flask_restx import Api, Resource, fields, reqparse

app = Flask(__name__)
api = Api(
app,
version='1.0',
title='Book Catalog API',
description='A simple Book Catalog API',
doc='/api/docs',
contact='[email protected]',
license='MIT',
license_url='https://opensource.org/licenses/MIT'
)

# Create namespaces for our API
books_ns = api.namespace('books', description='Book operations')
authors_ns = api.namespace('authors', description='Author operations')

# Define models
author_model = api.model('Author', {
'id': fields.Integer(readonly=True, description='The author unique identifier'),
'name': fields.String(required=True, description='Author name'),
'nationality': fields.String(description='Author nationality')
})

book_model = api.model('Book', {
'id': fields.Integer(readonly=True, description='The book unique identifier'),
'title': fields.String(required=True, description='Book title'),
'year': fields.Integer(description='Publication year'),
'author_id': fields.Integer(required=True, description='Author ID'),
'genre': fields.String(description='Book genre')
})

# Mock database
books = [
{"id": 1, "title": "The Great Gatsby", "year": 1925, "author_id": 1, "genre": "Classic"},
{"id": 2, "title": "To Kill a Mockingbird", "year": 1960, "author_id": 2, "genre": "Fiction"}
]

authors = [
{"id": 1, "name": "F. Scott Fitzgerald", "nationality": "American"},
{"id": 2, "name": "Harper Lee", "nationality": "American"}
]

# Parser for query parameters
book_parser = reqparse.RequestParser()
book_parser.add_argument('genre', type=str, help='Filter by genre')
book_parser.add_argument('year', type=int, help='Filter by publication year')

@books_ns.route('/')
class BookList(Resource):
@books_ns.doc('list_books')
@books_ns.marshal_list_with(book_model)
@books_ns.expect(book_parser)
def get(self):
"""List all books with optional filters"""
args = book_parser.parse_args()
result = books

if args['genre']:
result = [book for book in result if book['genre'] == args['genre']]
if args['year']:
result = [book for book in result if book['year'] == args['year']]

return result

@books_ns.doc('create_book')
@books_ns.expect(book_model)
@books_ns.marshal_with(book_model, code=201)
@books_ns.response(400, 'Validation Error')
def post(self):
"""Create a new book"""
# Implementation would validate input and add to database
return books[0], 201

@books_ns.route('/<int:id>')
@books_ns.response(404, 'Book not found')
@books_ns.param('id', 'The book identifier')
class Book(Resource):
@books_ns.doc('get_book')
@books_ns.marshal_with(book_model)
def get(self, id):
"""Fetch a book by ID"""
for book in books:
if book['id'] == id:
return book
books_ns.abort(404, f"Book {id} doesn't exist")

@books_ns.doc('delete_book')
@books_ns.response(204, 'Book deleted')
def delete(self, id):
"""Delete a book"""
# Implementation would delete from database
return '', 204

@authors_ns.route('/')
class AuthorList(Resource):
@authors_ns.doc('list_authors')
@authors_ns.marshal_list_with(author_model)
def get(self):
"""List all authors"""
return authors

@authors_ns.route('/<int:id>')
@authors_ns.response(404, 'Author not found')
@authors_ns.param('id', 'The author identifier')
class Author(Resource):
@authors_ns.doc('get_author')
@authors_ns.marshal_with(author_model)
def get(self, id):
"""Fetch an author by ID"""
for author in authors:
if author['id'] == id:
return author
authors_ns.abort(404, f"Author {id} doesn't exist")

@authors_ns.doc('get_author_books')
@authors_ns.marshal_list_with(book_model)
def get_books(self, id):
"""Get all books by this author"""
return [book for book in books if book['author_id'] == id]

@authors_ns.route('/<int:id>/books')
@authors_ns.response(404, 'Author not found')
@authors_ns.param('id', 'The author identifier')
class AuthorBooks(Resource):
@authors_ns.doc('get_author_books')
@authors_ns.marshal_list_with(book_model)
def get(self, id):
"""Get all books by this author"""
# Check if author exists
author_exists = False
for author in authors:
if author['id'] == id:
author_exists = True
break

if not author_exists:
authors_ns.abort(404, f"Author {id} doesn't exist")

return [book for book in books if book['author_id'] == id]

if __name__ == '__main__':
app.run(debug=True)

This example demonstrates a more complete API with:

  1. Multiple namespaces (books and authors)
  2. Related resources
  3. Query parameters
  4. HTTP status codes
  5. Error responses
  6. CRUD operations

Visit http://localhost:5000/api/docs to see the interactive documentation.

Hosting Your API Documentation

Once you've created your API documentation, you might want to host it separately from your API. Here are some options:

  1. Static Documentation: Export your Swagger JSON/YAML and use tools like ReDoc or Swagger UI to generate static HTML

  2. Dedicated Documentation Sites: Use platforms like ReadTheDocs, Docusaurus, or GitBook

  3. API Management Platforms: Services like Postman or Stoplight can host and manage your API documentation

Summary

In this guide, we've explored various ways to document your Flask APIs:

  1. Manual documentation using Markdown or other formats
  2. Automated documentation with Swagger/OpenAPI using:
    • Flask-RESTPlus (older)
    • Flask-RESTX (newer)
    • Flasgger

Good documentation is a crucial part of any API. It helps users understand your API, reduces support requests, and makes your API more accessible. By following the best practices outlined in this guide, you can create clear, comprehensive documentation for your Flask APIs.

Additional Resources

Exercises

  1. Add documentation to an existing Flask API using Flask-RESTX
  2. Create an API with different HTTP methods (GET, POST, PUT, DELETE) and document each one
  3. Document an API with authentication requirements
  4. Export your Swagger documentation to a static HTML file
  5. Create an API with multiple related resources (like our books and authors example) and document the relationships


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)