← All Articles
July 20, 2026
8 min read
AI · Legal Ops · Automation
AI for Legal Operations: Contract Review and Clause Extraction
Legal ops teams spend thousands of hours reading contracts, extracting terms, and flagging non-standard language. Claude can automate most of it. Here’s the architecture, the code, and the compliance considerations, drawn from two law firms and one in-house legal department.
The Use Case
Legal ops for a mid-market company might process:
- 50-100 incoming contracts per month (from vendors, partners, customers)
- 20-30 contract templates that need to be updated or audited
- Hundreds of existing contracts that need to be reviewed for compliance gaps
Manual process: a junior associate spends 30-45 minutes per contract reading it, extracting key terms (liability cap, indemnification clause, termination conditions), and flagging deviations from the company’s standard language.
Time cost: $25-40 per contract (at $50-80/hour billing). For 100 contracts, that’s $2500-4000 per month.
AI-assisted process: Claude extracts terms and flags deviations. The junior associate reviews Claude’s output (5-10 minutes per contract instead of 30-45 minutes) and either approves or corrects it.
New time cost: $5-10 per contract. Savings: $1500-3500 per month, or $18k-42k per year.
The Architecture
S3 → Lambda → Claude → DynamoDB → Review Queue
1. S3 Landing Zone
Contracts are uploaded to s3://company-legal/incoming/. A Lambda function is triggered on ObjectCreated events.
2. Lambda Processor
The function:
- Downloads the PDF from S3
- Extracts text (Claude can handle PDFs directly via the document API)
- Calls Claude with instructions to extract key contract terms
- Uses structured output to return a consistent JSON schema
- Stores results in DynamoDB for tracking
3. Key Terms Extraction
Define the terms your company cares about:
{
"contract_type": "NDA | MSA | SOW | Purchase Agreement",
"parties": ["company1", "company2"],
"effective_date": "2024-05-15",
"termination_clause": {
"notice_period_days": 30,
"automatic_renewal": true,
"termination_for_cause": "description"
},
"liability": {
"liability_cap_enabled": true,
"cap_amount": 100000,
"cap_currency": "USD",
"cap_relative_to": "annual fees"
},
"indemnification": {
"party_1_indemnifies": true,
"party_2_indemnifies": true,
"scope": "third-party IP claims"
},
"confidentiality": {
"nda_included": true,
"return_of_info_required": true,
"survival_period_days": 365
},
"deviations_from_template": [
{
"deviation": "Liability cap is $500k instead of $1M",
"severity": "medium",
"requires_approval": true
}
]
}
4. DynamoDB Storage
resource "aws_dynamodb_table" "contract_extractions" {
name = "contract-extractions"
billing_mode = "PAY_PER_REQUEST"
hash_key = "contract_id"
range_key = "timestamp"
attribute {
name = "contract_id"
type = "S"
}
attribute {
name = "timestamp"
type = "N"
}
}
5. Review Queue
Contracts with high-severity deviations go to a review queue (SQS or DynamoDB Streams). Junior associates approve or override Claude’s analysis.
The Code
import anthropic
import json
import boto3
import base64
from typing import TypedDict
s3 = boto3.client("s3")
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("contract-extractions")
class ContractTerms(TypedDict):
contract_type: str
parties: list[str]
effective_date: str
termination_clause: dict
liability: dict
indemnification: dict
confidentiality: dict
deviations_from_template: list[dict]
def extract_contract_terms(pdf_bucket: str, pdf_key: str) -> dict:
"""Extract key terms from a contract PDF using Claude."""
client = anthropic.Anthropic()
# Download PDF from S3
response = s3.get_object(Bucket=pdf_bucket, Key=pdf_key)
pdf_content = response["Body"].read()
# Encode to base64 for Claude
pdf_base64 = base64.standard_b64encode(pdf_content).decode("utf-8")
# Call Claude with structured output
message = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=2048,
messages=[
{
"role": "user",
"content": [
{
"type": "document",
"source": {
"type": "base64",
"media_type": "application/pdf",
"data": pdf_base64
}
},
{
"type": "text",
"text": """You are a legal contract analyst. Extract the following key terms from this contract:
1. Contract type (NDA, MSA, SOW, Purchase Agreement, etc.)
2. Parties to the contract
3. Effective date (YYYY-MM-DD format)
4. Termination clause details (notice period, auto-renewal, for-cause conditions)
5. Liability limitations (cap amount, cap relative to what, any exclusions)
6. Indemnification (who indemnifies whom, scope)
7. Confidentiality (NDA included?, return of info?, survival period)
Also compare against our standard template and flag any deviations. Severity levels:
- "critical": affects enforceability or our risk profile
- "medium": unexpected but not immediately problematic
- "low": style/formatting differences, minor changes
Return valid JSON."""
}
]
}
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "ContractTermsExtraction",
"schema": {
"type": "object",
"properties": {
"contract_type": {"type": "string"},
"parties": {"type": "array", "items": {"type": "string"}},
"effective_date": {"type": "string"},
"termination_clause": {
"type": "object",
"properties": {
"notice_period_days": {"type": "integer"},
"automatic_renewal": {"type": "boolean"},
"termination_for_cause": {"type": "string"}
}
},
"liability": {
"type": "object",
"properties": {
"liability_cap_enabled": {"type": "boolean"},
"cap_amount": {"type": "number"},
"cap_currency": {"type": "string"},
"cap_relative_to": {"type": "string"}
}
},
"indemnification": {
"type": "object",
"properties": {
"party_1_indemnifies": {"type": "boolean"},
"party_2_indemnifies": {"type": "boolean"},
"scope": {"type": "string"}
}
},
"confidentiality": {
"type": "object",
"properties": {
"nda_included": {"type": "boolean"},
"return_of_info_required": {"type": "boolean"},
"survival_period_days": {"type": "integer"}
}
},
"deviations_from_template": {
"type": "array",
"items": {
"type": "object",
"properties": {
"deviation": {"type": "string"},
"severity": {"type": "string"},
"requires_approval": {"type": "boolean"}
}
}
}
},
"required": ["contract_type", "parties", "termination_clause"]
}
}
}
)
return json.loads(message.content[0].text)
def lambda_handler(event, context):
bucket = event["Records"][0]["s3"]["bucket"]["name"]
key = event["Records"][0]["s3"]["object"]["key"]
contract_id = key.split("/")[-1].split(".")[0]
try:
# Extract terms
terms = extract_contract_terms(bucket, key)
# Store in DynamoDB
import time
table.put_item(
Item={
"contract_id": contract_id,
"timestamp": int(time.time()),
"extracted_terms": json.dumps(terms),
"source_file": key,
"requires_review": len([d for d in terms.get("deviations_from_template", []) if d["requires_approval"]]) > 0
}
)
return {
"statusCode": 200,
"body": json.dumps({"contract_id": contract_id, "extracted": True})
}
except Exception as e:
print(f"Error processing {key}: {str(e)}")
raise
Compliance Considerations
Legal ops teams worry about three things.
1. Data confidentiality
Contracts contain sensitive information. Ensure:
- Data is encrypted in transit (HTTPS to Claude API)
- Data is encrypted at rest in S3 and DynamoDB
- Access is restricted (IAM policies, not public)
- Claude’s API doesn’t retain your data for training (it doesn’t; see Anthropic’s privacy policy)
- Compliance with any applicable regulations (GDPR, SOC2, etc.)
2. Liability
Claude makes mistakes. It might misinterpret a liability cap or miss a critical clause. The system is a first-pass filter, not a replacement for human review. Make this clear in your process: “AI-assisted extraction. All critical terms require legal review.”
3. Audit trail
Keep records of:
- Which contracts were processed when
- What Claude extracted
- What the human reviewer approved or changed
- Who approved it
This is important for compliance audits and dispute resolution.
# Log all approvals to CloudTrail via Lambda execution role
import json
def log_approval(contract_id: str, approver: str, changes: list[dict]):
cloudwatch = boto3.client("logs")
cloudwatch.put_log_events(
logGroupName="/aws/legal-ops/contract-approvals",
logStreamName=f"{contract_id}",
logEvents=[
{
"timestamp": int(time.time() * 1000),
"message": json.dumps({
"contract_id": contract_id,
"approver": approver,
"approved_at": datetime.now().isoformat(),
"changes": changes
})
}
]
)
Cost Breakdown
For 100 contracts per month:
- Claude API: ~$1.50 per contract (2-page average, ~3000 tokens input). Total: $150/month.
- Lambda: $0.10/month (100 invocations).
- S3: $0.50/month.
- DynamoDB: $5/month (on-demand).
Total infrastructure: ~$157/month
Labor savings: $1500-3500/month (depending on extraction accuracy and downstream review time).
ROI: 10-22x month one, then improving.
What Still Needs Humans
- Final approval. Someone needs to sign off on key terms before the contract is executed.
- Negotiation strategy. Claude can extract the cap, but deciding whether to accept it is a business decision.
- Unusual contract types. If you get a contract Claude has never seen before (highly specialized, multi-party, conditional terms), human review from the start.
- Red flags. Claude might miss subtle issues that require domain expertise or knowledge of your company’s risk appetite.
Treat Claude as a paralegal who reads fast but sometimes misses nuances. Good paralegals catch that. Good processes catch that.
Getting Started
- Pick 20-30 representative contracts from the past year
- Have Claude extract terms from them
- Have a lawyer compare Claude’s extraction to what they’d have extracted
- Measure accuracy on key fields
- If accuracy > 85%, scale it up
Most firms see 85-95% accuracy on standard contract types. Non-standard or unusual language drops it to 70-80%, but even that saves significant time in review.
Get the free AI Readiness Checklist
15 questions to diagnose your team’s AI readiness, where you’ll see ROI fastest, and what to tackle first.
✓ Takes 5 minutes
✓ Actionable next steps
✓ No sales pitch
No spam. Unsubscribe anytime.
or
Ready to build AI that actually works?
Let’s talk about how SRE discipline transforms AI from a risky experiment into a reliable business system.
Book Your Free Discovery Call