This guide covers how to deploy the FastAPI wrapper in this repo so the contract review demo can run from a public website such as a Vercel-hosted portfolio.
It is written for the current implementation, not an idealized future state.
This repo contains two runnable surfaces:
- LangGraph Studio workflow for local graph inspection and resume flows
- FastAPI wrapper for browser-based demo usage
For the website demo, you are deploying the FastAPI app in api/main.py, not Vite, not LangGraph Studio itself.
The website frontend is expected to call:
POST /api/runPOST /api/resume/{thread_id}GET /api/runs/{thread_id}GET /api/pending-reviewsGET /api/health
These are important demo-day constraints:
- The API wrapper currently invokes the graph with
provider="openai". - If
OPENAI_API_KEYis missing andALLOW_HEURISTIC_FALLBACK=true, the demo can still run on the deterministic local fallback path. - Interrupt/resume state is stored in a SQLite-backed LangGraph checkpointer.
- Audit and report artifacts are written to local disk under
runtime/.
What this means in practice:
- For the website demo,
OPENAI_API_KEYis the most important provider credential to set. - Resume survives backend restarts as long as the same local checkpoint file is still available.
- Multi-instance scaling can still break interrupted-thread resume unless replicas share the same checkpoint database.
- Railway disk artifacts are useful for short-lived demos, but they are not durable production storage.
For a portfolio demo, the simplest reliable setup is:
- Deploy this backend repo to Railway.
- Keep it on a single instance.
- Point your Vercel portfolio site at the Railway URL with
VITE_API_URL. - Restrict CORS to your website domain and localhost.
This is a good fit for the repo as it exists today.
Before you deploy, make sure you have:
- A GitHub repo for this project
- A Railway account
- A Vercel project for your website
- An OpenAI API key if you want live LLM extraction
Optional but useful:
- A custom domain for the website
- A Railway custom domain for cleaner demos
Start from .env.example. For the website demo, these are the important variables.
OPENAI_API_KEYCORS_ALLOWED_ORIGINS
OPENAI_MODEL=gpt-4.1-miniALLOW_HEURISTIC_FALLBACK=true
OPENAI_BASE_URLDEFAULT_PROVIDERLANGGRAPH_CHECKPOINTERGEMINI_API_KEYGROK_API_KEYGEMINI_MODELGROK_MODELGROK_BASE_URL
The Studio graph supports openai, gemini, and grok, but the FastAPI wrapper currently hardcodes provider="openai" when the website calls /api/run.
That means:
OPENAI_API_KEYis the credential that matters for the website deployment todayDEFAULT_PROVIDERdoes not control the website API behavior- using Gemini or Grok from the website would require a small backend code change
Do this once before pushing to Railway.
- Install the API dependencies.
pip install -e ".[api]"- Copy the example env file and fill in at least the values you plan to use.
cp .env.example .env- Start the API locally.
uvicorn api.main:app --host 0.0.0.0 --port 8000- Verify the health endpoint.
curl http://localhost:8000/api/healthExpected response:
{"status":"ok","service":"contract-review-api"}- Optional: test the run endpoint with pasted text.
curl -X POST http://localhost:8000/api/run \
-F 'text=This agreement includes automatic renewal, indemnification, and unlimited liability.'If the backend responds with status: "interrupted" or status: "completed", the wrapper is working.
- Optional: confirm the reviewer inbox endpoint.
curl http://localhost:8000/api/pending-reviewsThis repo already includes:
Railway can deploy the repo directly from GitHub.
Push the latest backend repo state to GitHub.
In Railway:
- Create a new project.
- Choose "Deploy from GitHub repo".
- Select this repo.
- Let Railway build using the included Dockerfile.
Add these in Railway project settings.
Minimum recommended values:
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4.1-mini
ALLOW_HEURISTIC_FALLBACK=true
LANGGRAPH_CHECKPOINTER=sqlite
CORS_ALLOWED_ORIGINS=https://your-vercel-domain.vercel.app,https://your-custom-domain.com,http://localhost:5173If you do not want live model calls during early testing, you can omit OPENAI_API_KEY and rely on fallback, but the extraction quality will be simpler and more deterministic.
Railway should start the app with:
uvicorn api.main:app --host 0.0.0.0 --port $PORTThat start command is already defined in railway.toml.
After deployment, open:
https://your-railway-domain/api/health
You should get:
{"status":"ok","service":"contract-review-api"}Once Railway is live, configure the website repo.
In Vercel, add:
VITE_API_URL=https://your-railway-domainThen redeploy the website.
Without this variable, the hosted site intentionally disables the interactive demo so it does not try to call localhost in a visitor's browser.
Use this exact checklist after both deploys are live.
- Open
https://your-backend/api/health - Confirm the response is HTTP 200
- Confirm CORS includes your website domain
- Confirm
VITE_API_URLis set in Vercel - Load the website page with the contract review demo
- Start a run using pasted text
- Start a run using uploaded file input
- Verify a low-risk contract can complete without review
- Verify a high-risk contract reaches the interrupted human-review path
- Submit approve, edit, and reject review actions at least once
- Confirm the UI shows final status after resume
- Confirm
GET /api/runs/{thread_id}still returns the interrupted review state after a backend restart
- Try an unsupported file type and confirm the API rejects it cleanly
- Try a file larger than 5 MB and confirm the API returns the size error
- Temporarily remove
OPENAI_API_KEYand confirm fallback still produces a usable demo path ifALLOW_HEURISTIC_FALLBACK=true
Use this on the day of the portfolio demo.
- Confirm Railway service is awake and healthy
- Confirm Vercel deployment points to the correct backend URL
- Confirm
CORS_ALLOWED_ORIGINSincludes the exact website origin you will use - Confirm
OPENAI_API_KEYis present if you want real model-backed extraction - Confirm you are running a single backend instance
- Confirm the service has not restarted recently
- Preload one low-risk example and one high-risk example
- Use text input if you want the most controlled path
- Use file upload only after confirming upload behavior live
- Avoid scaling to multiple replicas during the demo
- Check
/api/health - Check Railway logs
- Check browser network requests for CORS or 4xx errors
- Retry with pasted text instead of file upload
- Fall back to heuristic mode if provider calls are failing
These are worth knowing ahead of time so they do not surprise you:
- Resume is durable only while the backend keeps access to
runtime/audit/checkpoints.sqlite3. - Multiple backend replicas are not safe for interrupt/resume unless they share the same checkpoint database.
- Runtime artifacts under
runtime/audit/andruntime/reports/are local to the container filesystem. - The API wrapper does not yet expose provider selection to the frontend.
- This deployment shape is appropriate for a demo and portfolio site, not a hardened production service.
If you want the demo to be more robust for public traffic, the next backend improvements should be:
- Move checkpoints and audit artifacts to shared durable storage.
- Add explicit provider selection to the API contract.
- Add authentication and stronger rate limiting.
- Upgrade from single-node SQLite to a shared production-grade checkpoint backend.
Cause:
VITE_API_URLis missing in Vercel
Fix:
- add
VITE_API_URL - redeploy the website
Cause:
CORS_ALLOWED_ORIGINSdoes not include the exact website origin
Fix:
- add the full origin including protocol
- redeploy or restart the backend if needed
Cause:
- the thread was already resumed
- the local checkpoint file is missing
- the request hit a different instance without shared checkpoint storage
Fix:
- rerun the demo flow from the beginning
- keep the service on one instance unless replicas share the same checkpoint DB
- confirm
runtime/audit/checkpoints.sqlite3still exists on the active instance
Cause:
- the backend fell back to heuristic extraction
Fix:
- set
OPENAI_API_KEY - verify outbound provider access
- keep
ALLOW_HEURISTIC_FALLBACK=trueas a safety net, not the primary path
[ ] Backend repo pushed to GitHub
[ ] Railway project created from repo
[ ] OPENAI_API_KEY set in Railway
[ ] OPENAI_MODEL set in Railway
[ ] ALLOW_HEURISTIC_FALLBACK=true set in Railway
[ ] LANGGRAPH_CHECKPOINTER=sqlite set in Railway
[ ] CORS_ALLOWED_ORIGINS includes Vercel domain and localhost
[ ] Railway deploy succeeds
[ ] /api/health returns 200
[ ] VITE_API_URL set in Vercel
[ ] Website redeployed
[ ] Text-input demo path tested
[ ] File-upload demo path tested
[ ] Interrupted review path tested
[ ] Resume path tested
[ ] /api/pending-reviews tested
[ ] Service kept to one instance for demo