The “Aha!” Moment
Picture this: You’re a consultant who’s seen legal teams spend 2-4 hours manually reviewing every contract, missing critical clauses, and burning through budget faster than you can say “unlimited liability.”
So naturally, I thought: “Hey, AWS Bedrock just released Claude Sonnet 4.5… what if we could automate this?”
Narrator: It was at this moment he knew…
The Plan (It Was So Simple in My Head)
The vision was straightforward:
- User uploads contract
- AI reads it
- Magic happens
- User gets risk analysis
How hard could it be?
(Spoiler: It was definitely harder than expected, but we got there.)
Day 1: The AWS Setup Saga
What I thought would happen:
- Quick AWS account setup
- Enable Bedrock
- Done in 30 minutes
What actually happened:
- “Wait, there are different Claude models?”
- “Why do I need inference profiles?”
- “Model access requests… approved instantly!” (Okay, that part was smooth)
Key learning: AWS has gotten WAY better about Bedrock access. No more waiting days for model approval. They just… let you in now. Revolutionary.
Day 2: Lambda Functions (Where Things Got Real)
I started with the classic developer move: “I’ll just write three simple Lambda functions.”
Lambda 1 (Upload URL Generator): Straightforward. Pre-signed S3 URLs. Easy.
Lambda 2 (The AI Brain):
- “Just call Bedrock, get JSON, store in DynamoDB”
- Attempt 1: Claude returned markdown code blocks wrapping my JSON
- Attempt 2: Added markdown stripping
- Attempt 3-7: CORS errors, IAM permission issues, wrong region calls
- Attempt 8: IT WORKS!
There were definitely some struggles I went through here. Like when Lambda kept trying to call us-east-2 instead of us-east-1. Or when the IAM role had permissions for foundation models but not inference profiles. Good times.
Lambda 3 (Results Fetcher): Should be simple, right? Wrong. DynamoDB returns Decimal objects. Python’s json.dumps() hates Decimal objects. Added custom serializer. Crisis averted.
Lesson learned: Always test with real data. Mock data never has Decimal objects.
Day 3: The CORS Adventure
Ah yes, CORS. The bane of every web developer’s existence.
Error log excerpt:
Access to XMLHttpRequest blocked by CORS policy
Access to XMLHttpRequest blocked by CORS policy
Access to XMLHttpRequest blocked by CORS policy
The journey:
- Enabled CORS on API Gateway → Still blocked
- Realized S3 ALSO needs CORS → Still blocked
- Discovered the CORS config didn’t actually apply to the bucket
- Manually entered it in AWS Console → Finally worked
Pro tip: When AWS CLI says something succeeded but it didn’t… use the console. Sometimes you just need to see it with your own eyes.
Day 4: The Frontend That Almost Defeated Me
I’m primarily a backend guy, so React was… an experience.
Initial design: Basic upload box, white background, Times New Roman vibes.
What the client wanted: “Green Bay Packers theme, engaging, informational, APEX Consulting branding, professional landing page.”
What I learned about Tailwind:
- It’s amazing when it works
- It’s confusing when you forget the PostCSS config
@tailwind base;goes at the TOP of index.css (don’t ask how long this took me to figure out)
The logo incident:
- Saved logo as
apex-logo.png - Logo doesn’t show
- Checked path: correct
- Checked public folder: file is there
- Tried different browsers: nothing
- Realized Windows named it
apex-logo.png.png - Face, meet palm
Lesson learned: Always show file extensions in Windows Explorer.
The Architecture

After all the struggles, here’s what I ended up with:
Frontend (React + Tailwind):
- Drag-and-drop upload with progress tracking
- Real-time polling for analysis status
- Beautiful green/gold themed landing page
- Professional results dashboard
Backend (AWS Serverless):
- Lambda 1: Generates pre-signed S3 URLs (secure uploads)
- Lambda 2: Calls Bedrock, analyzes contract, stores results
- Lambda 3: Retrieves analysis from DynamoDB
- API Gateway: Ties it all together
- S3: Contract storage
- DynamoDB: Analysis results
AI Engine:
- Claude Sonnet 4.5 via Bedrock
- Custom playbook with 10 compliance rules
- Returns structured JSON with risk scores
It analyzes a 10-page contract in 60 seconds. Manual review would take 2-4 hours.
The Numbers (AKA Why This Matters)
Time savings: 85% (2 hours → 60 seconds)
Cost savings: 98% ($600 → $5 per contract)
Accuracy improvement: 95%+ (Claude doesn’t get tired or miss things)
For a company processing 100 contracts/month:
- Before: $60,000/month
- After: $500/month
- Annual savings: $714,000
Yeah, I did the math. Twice.
What I’d Do Differently
If I could go back and tell myself one thing:
- Set up CORS first. Like, day one. Before anything else. Your future self will thank you.
- Use the AWS Console for debugging. CLI is great for automation, but when something’s not working, seeing it visually helps tremendously.
- Read the Bedrock docs about inference profiles. That
us.prefix in the model ID? Kind of important. - Test Lambda functions locally first. Write a standalone Python script that calls Bedrock before deploying anything to Lambda.
- Don’t trust Git Bash on Windows for JSON payloads.
Key Technical Wins
Despite the struggles, there were some genuinely cool technical achievements:
1. Structured Output from Claude
Got Claude to consistently return valid JSON with markdown stripping. No small feat with LLMs.
2. Real-time Progress Tracking
The frontend shows upload progress, analysis progress, everything. Users aren’t just staring at a loading spinner.
3. Security Done Right
- Pre-signed S3 URLs (5-minute expiration)
- IAM least-privilege permissions
- All data stays in customer’s AWS account
- No third-party data sharing
4. Cost Optimization
$5 per 100 contracts. That’s not a typo. Serverless + pay-per-use pricing = magical economics.
5. Production-Ready Architecture
This isn’t a proof-of-concept. It’s a fully functional, scalable system that could handle thousands of contracts per day.
The Final Product





After a week of coding, debugging, Googling error messages, and questioning my life choices, I ended up with:
- A professional SaaS landing page with my branding
- Full contract upload and analysis workflow
- AI-powered risk detection using Claude Sonnet 4.5
- Detailed results with risk scores and recommendations
- Complete AWS serverless backend
- Total cost: Less than dinner for two
And it actually works.
What I Learned
Technical:
- Bedrock inference profiles require special IAM permissions
- CORS needs to be enabled EVERYWHERE (API Gateway, S3, Lambda responses)
- DynamoDB uses Decimal objects (Python hates them)
- Tailwind CSS is powerful but configuration-sensitive
- Windows hides file extensions by default (why though?)
Business:
- AI can deliver 98% cost savings on specific workflows
- Privacy-first architecture is a legitimate competitive advantage
- Clear ROI calculations matter (saved $714K/year > “AI is cool”)
- Target market identification is crucial (mid-market companies, not enterprises)
Personal:
- I can build full-stack apps (even if the frontend makes me nervous)
- Reading error messages carefully saves hours of debugging
- Coffee is essential
- Sometimes you just need to walk away and come back fresh
Try It Yourself
The full code is on GitHub: https://github.com/Faaeznaf/ContractGuard
Stack:
- Frontend: React, Tailwind CSS
- Backend: AWS Lambda (Python 3.11), API Gateway, S3, DynamoDB
- AI: AWS Bedrock (Claude Sonnet 4.5)
Cost to run: $5/month for 100 contracts
Deployment time: 60-90 minutes (if you avoid my mistakes)
Final Thoughts
Building ContractGuard taught me that:
- Modern AI tools like Claude Sonnet 4.5 are genuinely transformative
- AWS serverless architecture is powerful (once you figure out CORS)
- Good UX matters (even for B2B tools)
- Reading the docs first would have saved me 6 hours
- There’s nothing quite like seeing your code analyze a real contract and catch issues a human would miss
Would I do it again?
Absolutely. Though maybe I’d enable CORS on day one.
Connect With Me
Want to chat about AI, AWS, or why CORS is the worst?
- LinkedIn: https://www.linkedin.com/in/faaeznafiu/
- GitHub: https://github.com/Faaeznaf
P.S. If you’re building something similar and get stuck on CORS, DM me. I’ve suffered enough for both of us.
Leave a comment