AI is rapidly transforming legal operations, especially in contract review and approval. As we covered in our Complete 2026 Guide to AI Workflow Automation APIs, automating legal document workflows demands a blend of robust AI, secure integrations, and compliance-ready architectures. In this deep-dive, we’ll walk you through building a practical, AI-powered contract review and approval workflow—step by step, with code, configuration, and real-world best practices.
You’ll learn how to set up a modern pipeline that ingests contracts, applies AI review for risk and clause analysis, routes for approval, and logs every step for compliance. This builder’s tutorial is designed for legal tech developers, solution architects, and process automation enthusiasts.
Prerequisites
- Technical Skills: Intermediate Python (3.11+), familiarity with REST APIs, JSON, and basic Docker usage.
- Tools:
- Python 3.11+
- Docker 26.x+
- OpenAI Python SDK (v2.4+), or Anthropic SDK (Claude 4, optional)
- FastAPI (0.110+)
- PostgreSQL (16+), SQLite, or any modern RDBMS
- Git (2.40+)
- Optional: Workflow automation platform (e.g., n8n, Zapier, or custom orchestrator)
- Accounts: Access to OpenAI or Anthropic API keys
- Knowledge: Understanding of contract basics and legal review concepts is helpful
1. Project Setup and Environment Configuration
-
Create Your Project Directory
mkdir ai-legal-contract-workflow && cd ai-legal-contract-workflow
-
Initialize a Git Repository
git init
-
Set Up a Python Virtual Environment
python3 -m venv venv source venv/bin/activate
-
Install Required Packages
pip install fastapi uvicorn[standard] openai sqlalchemy psycopg2-binary python-dotenv
Note: If using Anthropic’s Claude, replace
openaiwithanthropicand adjust the code accordingly. -
Create a
.envFile for Secretstouch .env echo "OPENAI_API_KEY=sk-..." >> .env echo "DATABASE_URL=postgresql://user:pass@localhost:5432/contracts" >> .envUpdate your API key and database URL as appropriate.
2. Database Schema: Contracts, Reviews, and Approvals
-
Define SQLAlchemy Models
Create
models.py:from sqlalchemy import Column, Integer, String, Text, DateTime, Enum, ForeignKey from sqlalchemy.orm import declarative_base, relationship from datetime import datetime Base = declarative_base() class Contract(Base): __tablename__ = 'contracts' id = Column(Integer, primary_key=True) title = Column(String(255)) content = Column(Text) status = Column(Enum('uploaded', 'in_review', 'approved', 'rejected', name='status_enum'), default='uploaded') created_at = Column(DateTime, default=datetime.utcnow) reviews = relationship("Review", back_populates="contract") class Review(Base): __tablename__ = 'reviews' id = Column(Integer, primary_key=True) contract_id = Column(Integer, ForeignKey('contracts.id')) reviewer = Column(String(255)) ai_findings = Column(Text) decision = Column(Enum('approve', 'reject', 'needs_revision', name='decision_enum')) reviewed_at = Column(DateTime, default=datetime.utcnow) contract = relationship("Contract", back_populates="reviews") -
Initialize the Database
python >>> from models import Base >>> from sqlalchemy import create_engine >>> engine = create_engine("postgresql://user:pass@localhost:5432/contracts") >>> Base.metadata.create_all(engine)This creates the necessary tables. For more on secure API gateway design, see How to Create a Secure API Gateway for AI Workflow Automation (2026 Edition).
3. Building the Document Ingestion API
-
Set Up FastAPI for Ingestion
Create
main.py:from fastapi import FastAPI, UploadFile, File, Form from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine from models import Base, Contract import os app = FastAPI() DATABASE_URL = os.getenv("DATABASE_URL") engine = create_engine(DATABASE_URL) Session = sessionmaker(bind=engine) @app.post("/contracts/upload") async def upload_contract(title: str = Form(...), file: UploadFile = File(...)): content = (await file.read()).decode('utf-8') session = Session() contract = Contract(title=title, content=content) session.add(contract) session.commit() session.refresh(contract) session.close() return {"contract_id": contract.id, "status": contract.status}Test: Use
curlto upload a sample contract:curl -F "title=Sample NDA" -F "file=@nda.txt" http://localhost:8000/contracts/upload -
Run the API Server
uvicorn main:app --reloadVisit
http://localhost:8000/docsfor the interactive Swagger UI.
4. Integrating AI for Automated Contract Review
-
Install and Configure OpenAI SDK
pip install openai
Add to your
main.py:import openai from dotenv import load_dotenv load_dotenv() openai.api_key = os.getenv("OPENAI_API_KEY") -
Create an AI Review Endpoint
from models import Review, Contract from sqlalchemy.orm import sessionmaker @app.post("/contracts/{contract_id}/review") def review_contract(contract_id: int): session = Session() contract = session.query(Contract).get(contract_id) if not contract: session.close() return {"error": "Contract not found"} # Call OpenAI prompt = f"Review this contract for legal risks and missing clauses:\n\n{contract.content}\n\nReturn a summary and a decision (approve/reject/needs_revision)." response = openai.ChatCompletion.create( model="gpt-5-turbo", messages=[{"role": "system", "content": "You are a senior legal contract reviewer."}, {"role": "user", "content": prompt}], temperature=0.1, max_tokens=1024 ) findings = response['choices'][0]['message']['content'] # Simple parse (customize as needed) decision = "needs_revision" if "approve" in findings.lower(): decision = "approve" elif "reject" in findings.lower(): decision = "reject" review = Review(contract_id=contract_id, reviewer="AI", ai_findings=findings, decision=decision) contract.status = "in_review" session.add(review) session.commit() session.close() return {"ai_findings": findings, "decision": decision}Test:
curl -X POST http://localhost:8000/contracts/1/review -
Automate Review Trigger (Optional)
To automatically trigger AI review on upload, call the review endpoint from the upload handler or use a workflow orchestrator.
For advanced orchestration, see How to Automate Complex Approval Chains Using AI in 2026.
5. Human-in-the-Loop Approval Workflow
-
Expose Approval Endpoints
from fastapi import Body @app.post("/contracts/{contract_id}/approve") def approve_contract(contract_id: int, approver: str = Body(...)): session = Session() contract = session.query(Contract).get(contract_id) if not contract: session.close() return {"error": "Contract not found"} contract.status = "approved" review = Review(contract_id=contract_id, reviewer=approver, ai_findings="Manual approval", decision="approve") session.add(review) session.commit() session.close() return {"status": "approved"} @app.post("/contracts/{contract_id}/reject") def reject_contract(contract_id: int, approver: str = Body(...), reason: str = Body(...)): session = Session() contract = session.query(Contract).get(contract_id) if not contract: session.close() return {"error": "Contract not found"} contract.status = "rejected" review = Review(contract_id=contract_id, reviewer=approver, ai_findings=reason, decision="reject") session.add(review) session.commit() session.close() return {"status": "rejected"}Test:
curl -X POST -H "Content-Type: application/json" -d '{"approver":"legal.manager@example.com"}' http://localhost:8000/contracts/1/approve -
Audit Trail and History
You can list all reviews for a contract:
@app.get("/contracts/{contract_id}/reviews") def get_reviews(contract_id: int): session = Session() reviews = session.query(Review).filter_by(contract_id=contract_id).all() session.close() return [{"reviewer": r.reviewer, "decision": r.decision, "findings": r.ai_findings, "date": r.reviewed_at} for r in reviews]
6. Workflow Orchestration & Notifications
-
Automate with n8n or Custom Logic
Use n8n or similar to trigger Slack/email notifications on status changes. Example: Webhook node → Email node.
For custom Python, add a notification function after status change:
import smtplib def send_notification(email, subject, message): # Simplified; use robust libraries in production with smtplib.SMTP('smtp.example.com') as server: server.login('user', 'pass') server.sendmail('from@example.com', email, f"Subject: {subject}\n\n{message}") -
Integrate with DMS or Knowledge Base
On approval, push the contract to your Document Management System or Legal Knowledge Base via API. For detailed integration steps, see Integrating Knowledge Bases with AI Workflow Automation: Step-by-Step Guide.
7. Security, Compliance, and Logging
-
API Authentication
Add basic authentication middleware or OAuth2 for endpoints. Example with FastAPI:
from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi import Depends security = HTTPBasic() @app.get("/secure-endpoint") def secure_endpoint(credentials: HTTPBasicCredentials = Depends(security)): # Validate credentials here return {"user": credentials.username}For deeper coverage of AI workflow API security, see Securing AI Workflow APIs: The Top Emerging Threats and How to Address Them in 2026.
-
Audit Logging
Log every action (upload, review, approval) with timestamp and user info. Use Python’s
loggingmodule or a centralized log system.import logging logging.basicConfig(filename='audit.log', level=logging.INFO) def log_action(user, action, contract_id): logging.info(f"{datetime.utcnow()} | {user} | {action} | contract_id={contract_id}") -
Data Retention and Compliance
Ensure contracts, reviews, and logs are retained per your jurisdiction’s legal requirements. For EU compliance, see AI Workflow Automation and the 2026 EU Digital Markets Regulation: New Compliance Hurdles for SaaS.
Common Issues & Troubleshooting
- OpenAI/Anthropic API Errors: Check API key, rate limits, and model availability. For best practices, see AI Workflow API Rate Limits: Best Practices to Avoid Bottlenecks in 2026.
-
Database Connection Issues: Verify
DATABASE_URL, database server status, and user permissions. - File Upload Failures: Ensure correct MIME types and file size limits in FastAPI settings.
- AI Review Output Parsing: The AI’s output may not always be structured; refine your prompt or use regex/JSON output mode for consistency.
- Authentication Problems: Test credentials and consider using OAuth2 or JWT for production use.
- Notification Delivery Issues: Check SMTP/Slack API credentials, network firewalls, and error logs.
Next Steps
- Enhance AI Review Accuracy: Experiment with prompt engineering and model selection. See Best Prompt Engineering Techniques for Workflow Automation APIs in 2026.
- Add Advanced Approval Chains: Implement multi-stage or conditional approvals. For a full guide, see How to Automate Complex Approval Chains Using AI in 2026.
- Integrate with Enterprise Platforms: Connect your workflow to SAP, Salesforce, or DMS. See Integrating AI Workflow Automation With SAP Systems: Best Practices for 2026.
- Productionize and Monitor: Containerize with Docker, deploy on Kubernetes, and add monitoring/alerting.
- Explore Related Tutorials: For a broader perspective, see How to Build an Automated Document Approval Workflow Using AI (2026 Step-by-Step) and How to Build Automated Legal Intake Workflows Using AI in 2026.
By following this tutorial, you’ve built a foundational AI legal contract workflow automation system—ready to adapt for real-world legal teams. For further reading on architecture, integration, and scaling, explore our parent pillar guide.