← All Articles

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:

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:

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:

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:

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:

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

Treat Claude as a paralegal who reads fast but sometimes misses nuances. Good paralegals catch that. Good processes catch that.

Getting Started

  1. Pick 20-30 representative contracts from the past year
  2. Have Claude extract terms from them
  3. Have a lawyer compare Claude’s extraction to what they’d have extracted
  4. Measure accuracy on key fields
  5. 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