backend/building-api-routes/SKILL.md
Build RESTful Flask routes with input validation, consistent response formatting, and pagination.
npx skillsauth add 7a336e6e/skills Building API RoutesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Create RESTful API endpoints in Flask that follow consistent URL conventions, validate all input, return uniform JSON responses, and support pagination for list endpoints.
Map HTTP methods to operations on resources:
| Method | URL Pattern | Action | Status Code | |--------|-------------------------------|--------------|-------------| | GET | /api/v1/resources | List all | 200 | | GET | /api/v1/resources/<id> | Get one | 200 | | POST | /api/v1/resources | Create | 201 | | PUT | /api/v1/resources/<id> | Full update | 200 | | PATCH | /api/v1/resources/<id> | Partial update | 200 | | DELETE | /api/v1/resources/<id> | Delete | 204 |
Use plural nouns for resource names. Never put verbs in URLs.
/api/v1/users
/api/v1/users/<int:user_id>
/api/v1/users/<int:user_id>/tasks
Validate incoming data before it reaches business logic. Use marshmallow schemas or pydantic models.
from marshmallow import Schema, fields, validate, ValidationError
class CreateUserSchema(Schema):
email = fields.Email(required=True)
name = fields.String(required=True, validate=validate.Length(min=1, max=255))
role = fields.String(validate=validate.OneOf(["admin", "member", "viewer"]))
Apply validation in the route handler:
@users_bp.route("/", methods=["POST"])
def create_user():
schema = CreateUserSchema()
try:
data = schema.load(request.get_json())
except ValidationError as err:
return jsonify({"error": {"code": "VALIDATION_ERROR", "message": "Invalid input", "details": err.messages}}), 422
user = user_service.create(data)
return jsonify({"data": user}), 201
Every endpoint returns a consistent JSON envelope:
{
"data": {},
"error": null,
"meta": {}
}
data and optionally meta (pagination info, counts).error with code, message, and optional details.For list endpoints, support offset/limit pagination:
@users_bp.route("/", methods=["GET"])
def list_users():
page = request.args.get("page", 1, type=int)
per_page = request.args.get("per_page", 20, type=int)
per_page = min(per_page, 100) # Cap maximum
pagination = User.query.paginate(page=page, per_page=per_page, error_out=False)
return jsonify({
"data": [user.to_dict() for user in pagination.items],
"meta": {
"page": pagination.page,
"per_page": pagination.per_page,
"total": pagination.total,
"pages": pagination.pages,
}
}), 200
For cursor-based pagination (better for large or frequently changing datasets):
@users_bp.route("/", methods=["GET"])
def list_users():
cursor = request.args.get("cursor", None)
limit = request.args.get("limit", 20, type=int)
limit = min(limit, 100)
query = User.query.order_by(User.id)
if cursor:
query = query.filter(User.id > cursor)
users = query.limit(limit + 1).all()
has_next = len(users) > limit
users = users[:limit]
next_cursor = users[-1].id if has_next and users else None
return jsonify({
"data": [u.to_dict() for u in users],
"meta": {
"next_cursor": next_cursor,
"has_next": has_next,
}
}), 200
from flask import Blueprint, jsonify, request
from marshmallow import ValidationError
from app.routes.users.schemas import CreateUserSchema, UpdateUserSchema
from app.routes.users.services import user_service
users_bp = Blueprint("users", __name__)
@users_bp.route("/", methods=["GET"])
def list_users():
page = request.args.get("page", 1, type=int)
per_page = min(request.args.get("per_page", 20, type=int), 100)
result = user_service.list_paginated(page, per_page)
return jsonify({"data": result["items"], "meta": result["meta"]}), 200
@users_bp.route("/<int:user_id>", methods=["GET"])
def get_user(user_id):
user = user_service.get_or_404(user_id)
return jsonify({"data": user}), 200
@users_bp.route("/", methods=["POST"])
def create_user():
schema = CreateUserSchema()
try:
data = schema.load(request.get_json())
except ValidationError as err:
return jsonify({"error": {"code": "VALIDATION_ERROR", "message": "Invalid input", "details": err.messages}}), 422
user = user_service.create(data)
return jsonify({"data": user}), 201
@users_bp.route("/<int:user_id>", methods=["PUT"])
def replace_user(user_id):
schema = CreateUserSchema()
try:
data = schema.load(request.get_json())
except ValidationError as err:
return jsonify({"error": {"code": "VALIDATION_ERROR", "message": "Invalid input", "details": err.messages}}), 422
user = user_service.update(user_id, data)
return jsonify({"data": user}), 200
@users_bp.route("/<int:user_id>", methods=["PATCH"])
def update_user(user_id):
schema = UpdateUserSchema()
try:
data = schema.load(request.get_json())
except ValidationError as err:
return jsonify({"error": {"code": "VALIDATION_ERROR", "message": "Invalid input", "details": err.messages}}), 422
user = user_service.partial_update(user_id, data)
return jsonify({"data": user}), 200
@users_bp.route("/<int:user_id>", methods=["DELETE"])
def delete_user(user_id):
user_service.delete(user_id)
return "", 204
data, error, meta)./api/v1/getUsers or /api/v1/createUser).When generating routes, produce the route file, the schema file, and the service file as separate code blocks. Include the blueprint registration line for create_app().
development
Implement features using the Red-Green-Refactor cycle to ensure testability and correctness from the start.
data-ai
Manage the `tasks.md` ledger with strict locking and collision avoidance protocols to allow multiple agents to work in parallel safely.
development
The git-workflow skill defines branching conventions, commit message formats, and pull request standards that all agents must follow for consistent version control.
development
The environment-config skill standardizes how agents manage environment variables, secrets, and application configuration across local development and deployed environments.