Docgenie-API / api /TESTING.md
Ahadhassan-2003
deploy: update HF Space
883ffeb
# Testing Guide: DocGenie API
Complete guide for testing the document generation API endpoints with Google Drive integration.
## Table of Contents
1. [Prerequisites](#prerequisites)
2. [Quick Start](#quick-start)
3. [Getting Google Drive Token](#getting-google-drive-token)
4. [Testing Async API](#testing-async-api)
5. [Testing Sync PDF API](#testing-sync-pdf-api)
6. [Manual Testing with cURL](#manual-testing-with-curl)
7. [Frontend Integration Example](#frontend-integration-example)
8. [Troubleshooting](#troubleshooting)
---
## Prerequisites
### 1. Start Required Services
```bash
# Terminal 1: Start Redis
## Option A: Local Redis (Recommended for Development)
# Install Redis (Ubuntu/Debian)
sudo apt-get update && sudo apt-get install redis-server -y
sudo systemctl start redis-server
sudo systemctl enable redis-server
# Verify Redis is running
redis-cli ping # Should return "PONG"
## Option B: Docker (if Docker is installed)
# docker run -d -p 6379:6379 --name redis redis:7-alpine
# Terminal 2: Start FastAPI Server
cd docgenie/api
python main.py
# Terminal 3: Start RQ Worker
cd docgenie/api
./start_worker.sh
```
### 2. Configure Environment
Make sure your `api/.env` file has:
```bash
# Required
ANTHROPIC_API_KEY=your_claude_api_key
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=your_supabase_key
REDIS_URL=redis://localhost:6379/0
# Optional (for token refresh)
GOOGLE_CLIENT_ID=your_client_id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your_client_secret
```
### 3. Create Supabase Tables
Run the SQL from [DEPLOYMENT.md](DEPLOYMENT.md#32-create-database-schema) in your Supabase SQL Editor.
---
## Quick Start
### Option 1: Using Test Script (Easiest)
```bash
# Get Google Drive token first (one-time setup)
python api/test_get_google_token.py \
--client-id YOUR_CLIENT_ID \
--client-secret YOUR_CLIENT_SECRET
# Copy the access token, then run test
python api/test_async_api.py --google-token YOUR_ACCESS_TOKEN
```
### Option 2: Using OAuth Playground (Quick Test)
1. Go to [OAuth Playground](https://developers.google.com/oauthplayground/)
2. Configure with your credentials
3. Get access token
4. Run test script with token
---
## Getting Google Drive Token
### Method 1: Using Helper Script (Recommended)
Our helper script automates the OAuth flow:
```bash
cd docgenie/api
python test_get_google_token.py \
--client-id YOUR_GOOGLE_CLIENT_ID \
--client-secret YOUR_GOOGLE_CLIENT_SECRET
```
**What it does:**
1. Opens browser for Google authorization
2. Starts local server on port 8080 for callback
3. Exchanges authorization code for tokens
4. Displays access token and refresh token
**Output:**
```
Access Token: ya29.a0AfH6SMBx...
Refresh Token: 1//0gw...
```
### Method 2: OAuth Playground (No Code)
1. **Go to**: https://developers.google.com/oauthplayground/
2. **Configure Credentials**:
- Click gear icon (βš™) in top right
- Check "Use your own OAuth credentials"
- Enter your Client ID and Client Secret
3. **Authorize API**:
- In left panel, scroll to "Drive API v3"
- Select: `https://www.googleapis.com/auth/drive.file`
- Click "Authorize APIs"
- Sign in with your Google account
4. **Get Token**:
- Click "Exchange authorization code for tokens"
- Copy the "Access token" value
### Method 3: Manual cURL (For Advanced Users)
**Step 1: Get Authorization Code**
Open this URL in browser (replace YOUR_CLIENT_ID):
```
https://accounts.google.com/o/oauth2/v2/auth?client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:8080&response_type=code&scope=https://www.googleapis.com/auth/drive.file&access_type=offline&prompt=consent
```
**Step 2: Exchange Code for Token**
After authorization, you'll be redirected to:
```
http://localhost:8080/?code=AUTHORIZATION_CODE
```
Exchange the code:
```bash
curl -X POST https://oauth2.googleapis.com/token \
-d "code=AUTHORIZATION_CODE" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "redirect_uri=http://localhost:8080" \
-d "grant_type=authorization_code"
```
Response:
```json
{
"access_token": "ya29.a0AfH6SMBx...",
"refresh_token": "1//0gw...",
"expires_in": 3600,
"token_type": "Bearer"
}
```
---
## Testing Async API
The async API (`/generate/async`) is optimized for batch processing with 50% cost savings. Jobs are queued and processed in the background, with status polling.
### Full Automated Test
```bash
cd docgenie/api
# Set token as environment variable
export GOOGLE_DRIVE_TOKEN="ya29.a0AfH6SMBx..."
# Run test (generates 2 documents by default)
python test_async_api.py
# Or pass token directly
python test_async_api.py --google-token "ya29.a0AfH6SMBx..."
```
**Test Flow:**
1. βœ“ Health check
2. βœ“ Submit async job
3. βœ“ Poll status (every 30 seconds)
4. βœ“ List user jobs
5. βœ“ Display Google Drive link
**Expected Output:**
```
================================================================================
ASYNC API TEST SUITE
================================================================================
Base URL: http://localhost:8000
User ID: 1
Documents to Generate: 2
================================================================================
============================================================
1. Testing API Health
============================================================
βœ“ API is healthy: {'status': 'healthy', 'version': '1.0.0'}
============================================================
2. Submitting Async Job
============================================================
Payload:
User ID: 1
Seed Images: 1
Num Solutions: 2
Google Token: ya29.a0AfH6SMBx...
βœ“ Job submitted successfully!
Request ID: 550e8400-e29b-41d4-a716-446655440000
Status: queued
Estimated Time: 10 minutes
Poll URL: /jobs/550e8400-e29b-41d4-a716-446655440000/status
============================================================
3. Polling Job Status
============================================================
Polling every 30 seconds (max 60 attempts)
Status flow: queued β†’ processing β†’ generating β†’ completed/failed
[12:00:00] Poll 1/60: QUEUED
[12:00:30] Poll 2/60: PROCESSING - Creating batch request...
[12:01:00] Poll 3/60: GENERATING - Batch submitted to Claude...
[12:08:30] Poll 17/60: GENERATING - Polling batch status...
[12:15:00] Poll 30/60: COMPLETED
============================================================
βœ“ JOB COMPLETED!
============================================================
Download URL: https://drive.google.com/file/d/abc123xyz/view?usp=sharing
File Size: 15.4 MB
Document Count: 2
Created: 2026-02-28T12:00:00Z
Completed: 2026-02-28T12:15:00Z
================================================================================
TEST SUMMARY
================================================================================
βœ“ ALL TESTS PASSED!
Your documents are available at:
https://drive.google.com/file/d/abc123xyz/view?usp=sharing
Next steps:
1. Open the Google Drive link in your browser
2. Download the ZIP file
3. Extract and verify generated documents
```
### Test Options
```bash
# Custom number of documents
python test_async_api.py --google-token TOKEN --num-solutions 5
# Custom API URL (if deployed)
python test_async_api.py --google-token TOKEN --base-url https://api.yourdomain.com
# Different user ID
python test_async_api.py --google-token TOKEN --user-id 42
# With refresh token
python test_async_api.py \
--google-token ACCESS_TOKEN \
--google-refresh-token REFRESH_TOKEN
# Show help for getting token
python test_async_api.py --help-token
```
---Testing Sync PDF API
The sync PDF API (`/generate/pdf`) returns results immediately (20-60s) and supports three modes of operation. Perfect for smaller batch sizes and real-time workflows.
### Three Operating Modes
**Mode 1: Quick Demo (No Tracking)**
- Returns ZIP immediately
- No Supabase records created
- Perfect for quick testing and demos
- No user_id required
**Mode 2: Demo with Tracking**
- Returns ZIP immediately
- Creates Supabase record for tracking
- Can poll status during generation
- Requires user_id
**Mode 3: Full Production**
- Returns ZIP immediately
- Creates Supabase record
- Uploads to Google Drive in background
- Requires user_id + google_drive_token
- Best for production use
### Full Automated Test
```bash
cd docgenie/api
# Mode 1: Quick demo (no tracking)
python test_sync_pdf_api.py
# Mode 2: Demo with tracking
python test_sync_pdf_api.py --user-id 123
# Mode 3: Full production (tracking + GDrive)
python test_sync_pdf_api.py \
--user-id 123 \
--google-token "ya29.a0AfH6SMBx..." \
--google-refresh-token "1//0gw..."
```
**Test Flow for All Modes:**
1. βœ“ Health check
2. βœ“ Test Mode 1: Quick demo (always runs)
3. βœ“ Test Mode 2: With tracking (if user_id provided)
4. βœ“ Test Mode 3: Full production (if user_id + token provided)
5. βœ“ Validate ZIP contents
6. βœ“ Test status polling (Modes 2 & 3)
7. βœ“ Verify GDrive upload (Mode 3)
**Expected Output:**
```
================================================================================
DocGenie /generate/pdf Endpoint Test Suite
================================================================================
================================================================================
1. Testing API Health
================================================================================
βœ“ API is healthy: {'status': 'healthy', 'version': '1.0.0'}
================================================================================
2. Testing Mode 1: Quick Demo (No Tracking)
================================================================================
This mode returns ZIP immediately without creating Supabase records.
Use for quick testing and demos.
Payload:
Seed Images: 1
Num Solutions: 1
User ID: None (no tracking)
Google Token: None
⏳ Calling /generate/pdf (expect 20-60 seconds)...
βœ“ Response received in 42.3 seconds
Response Headers:
Content-Type: application/zip
Content-Disposition: attachment; filename=docgenie_documents.zip
X-Request-ID: NOT SET (expected in mode 1)
X-Status-URL: NOT SET (expected in mode 1)
βœ“ ZIP file size: 145.2 KB
βœ“ ZIP contains 18 files:
- README.md
- metadata.json
- analysis/document_1.json
- annotations/gt/document_1.json
- bbox/bbox_pdf/word/document_1.json
- html/document_1.css
- html/document_1.html
- img/document_1.png
- pdf/pdf_final/document_1.pdf
- pdf/pdf_initial/document_1.pdf
βœ“ Contains metadata.json
βœ“ Contains README.md
βœ… Mode 1 (Quick Demo) Test PASSED
⚑ Fast response: 42.3s
πŸ“¦ Valid ZIP file
βœ“ No tracking overhead
================================================================================
3. Testing Mode 2: Demo with Progress Tracking
================================================================================
This mode returns ZIP immediately AND creates Supabase record.
Client can poll /jobs/{request_id}/status during generation.
Payload:
User ID: 123 (tracking enabled)
Seed Images: 1
Num Solutions: 2
Google Token: None
⏳ Calling /generate/pdf (expect 20-60 seconds)...
βœ“ Response received in 58.7 seconds
Response Headers:
Content-Type: application/zip
βœ“ X-Request-ID: 550e8400-e29b-41d4-a716-446655440000
βœ“ X-Status-URL: /jobs/550e8400-e29b-41d4-a716-446655440000/status
βœ“ ZIP file size: 287.4 KB
βœ“ ZIP contains 32 files
βœ“ Found 4 PDF files
⏳ Testing status polling endpoint...
βœ“ Status endpoint working:
Request ID: 550e8400-e29b-41d4-a716-446655440000
Status: completed
Created: 2026-03-01T10:15:00Z
Updated: 2026-03-01T10:15:58Z
βœ“ Job marked as completed
βœ… Mode 2 (Tracking) Test PASSED
⚑ Fast response: 58.7s
πŸ“¦ Valid ZIP file
πŸ“Š Progress tracking enabled
βœ“ Can poll status during generation
================================================================================
4. Testing Mode 3: Full Production (Tracking + GDrive Upload)
================================================================================
This mode returns ZIP immediately AND uploads to Google Drive in background.
Best for production use with full tracking and backup.
Payload:
User ID: 123
Google Token: ya29.a0AfH6SMBx...
Google Refresh: Yes
Seed Images: 1
Num Solutions: 1
⏳ Calling /generate/pdf (expect 20-60 seconds)...
βœ“ Response received in 45.1 seconds
Response Headers:
βœ“ X-Request-ID: 660f9511-f3ac-52e5-b827-557766551111
βœ“ X-Status-URL: /jobs/660f9511-f3ac-52e5-b827-557766551111/status
βœ“ ZIP file size: 151.8 KB
βœ“ ZIP contains 18 files
⏳ ZIP returned immediately, GDrive upload happening in background...
(This doesn't block the response)
⏳ Waiting 10 seconds for background GDrive upload...
βœ“ Status after background upload:
Status: completed
βœ“ GDrive URL: https://drive.google.com/file/d/abc123xyz/view?usp=...
βœ“ Background upload completed!
βœ… Mode 3 (Full Production) Test PASSED
⚑ Fast response: 45.1s (GDrive doesn't block)
πŸ“¦ Valid ZIP file delivered immediately
πŸ“Š Progress tracking enabled
☁️ Google Drive backup scheduled
βœ“ Production-ready configuration
================================================================================
TEST SUMMARY
================================================================================
βœ… health: PASSED
βœ… mode_1: PASSED
βœ… mode_2: PASSED
βœ… mode_3: PASSED
4/4 tests passed
πŸŽ‰ All tests passed!
================================================================================
```
### Test Options
```bash
# Mode 1 only (default)
python test_sync_pdf_api.py
# Mode 2 with custom user ID
python test_sync_pdf_api.py --user-id 456
# Mode 3 with custom API URL
python test_sync_pdf_api.py \
--base-url https://api.yourdomain.com \
--user-id 123 \
--google-token TOKEN \
--google-refresh-token REFRESH_TOKEN
```
### Comparing Sync vs Async
| Feature | Sync (`/generate/pdf`) | Async (`/generate/async`) |
|---------|------------------------|---------------------------|
| **Response Time** | 20-60 seconds | 5-30 minutes |
| **Best For** | 1-3 documents | 5-50+ documents |
| **Cost** | Standard API pricing | 50% cheaper (Batch API) |
| **Result Delivery** | Direct ZIP download | Google Drive upload |
| **Progress Tracking** | Optional (Modes 2 & 3) | Always enabled |
| **Use Case** | Real-time workflows, demos | Bulk generation, scheduled jobs |
**When to use Sync:**
- Generating 1-3 documents
- Need immediate results
- Real-time user interactions
- Quick testing and demos
**When to use Async:**
- Generating 5+ documents
- Cost optimization (50% savings)
- Background/scheduled processing
- Large batch jobs
---
## Manual Testing with cURL
### Async API (`/generate/async`)
#### 1. Submit Async Job
```bash
curl -X POST http://localhost:8000/generate/async \
-H "Content-Type: application/json" \
-d '{
"user_id": 1,
"google_drive_token": "ya29.a0AfH6SMBx...",
"seed_images": ["https://ocr.space/Content/Images/receipt-ocr-original.webp"],
"prompt_params": {
"language": "English",
"doc_type": "receipts",
"num_solutions": 2,
"enable_handwriting": false,
"enable_visual_elements": false,
"output_detail": "minimal"
}
}'
```
**Response:**
```json
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "queued",
"estimated_time_minutes": 10,
"poll_url": "/jobs/550e8400-e29b-41d4-a716-446655440000/status",
"created_at": "2026-02-28T12:00:00Z"
}
```
#### 2. Check Job Status
```bash
curl http://localhost:8000/jobs/550e8400-e29b-41d4-a716-446655440000/status
```
**Response (Processing):**
```json
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "processing",
"created_at": "2026-02-28T12:00:00Z",
"updated_at": "2026-02-28T12:02:00Z",
"progress": "Creating batch request..."
}
```
**Response (Completed):**
```json
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"created_at": "2026-02-28T12:00:00Z",
"updated_at": "2026-02-28T12:15:00Z",
"download_url": "https://drive.google.com/file/d/abc123xyz/view?usp=sharing",
"file_size_mb": 15.4,
"document_count": 2
}
```
#### 3. List User Jobs
```bash
curl "http://localhost:8000/jobs/user/1?limit=10&offset=0"
```
**Response:**
```json
{
"user_id": 1,
"jobs": [
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"created_at": "2026-02-28T12:00:00Z",
"download_url": "https://drive.google.com/file/d/abc123xyz/view"
}
],
"count": 1,
"limit": 10,
"offset": 0
}
```
### Sync PDF API (`/generate/pdf`)
#### Mode 1: Quick Demo (No Tracking)
```bash
curl -X POST http://localhost:8000/generate/pdf \
-H "Content-Type: application/json" \
-d '{
"seed_images": ["https://ocr.space/Content/Images/receipt-ocr-original.webp"],
"prompt_params": {
"language": "English",
"doc_type": "receipts",
"num_solutions": 1,
"enable_handwriting": false,
"enable_visual_elements": false,
"output_detail": "minimal"
}
}' \
--output documents.zip
```
**Response:**
- Returns ZIP file directly (binary)
- No tracking headers
- File saved as `documents.zip`
#### Mode 2: Demo with Tracking
```bash
curl -X POST http://localhost:8000/generate/pdf \
-H "Content-Type: application/json" \
-d '{
"user_id": 123,
"seed_images": ["https://ocr.space/Content/Images/receipt-ocr-original.webp"],
"prompt_params": {
"language": "English",
"doc_type": "business documents",
"num_solutions": 2,
"enable_handwriting": false,
"output_detail": "minimal"
}
}' \
--output documents.zip \
-D headers.txt
```
**Response:**
- Returns ZIP file directly (binary)
- Headers saved to `headers.txt` contain:
- `X-Request-ID: 550e8400-e29b-41d4-a716-446655440000`
- `X-Status-URL: /jobs/550e8400-e29b-41d4-a716-446655440000/status`
**Check Status:**
```bash
# Extract request_id from headers.txt, then:
curl http://localhost:8000/jobs/550e8400-e29b-41d4-a716-446655440000/status
```
#### Mode 3: Full Production (Tracking + GDrive)
```bash
curl -X POST http://localhost:8000/generate/pdf \
-H "Content-Type: application/json" \
-d '{
"user_id": 123,
"google_drive_token": "ya29.a0AfH6SMBx...",
"google_drive_refresh_token": "1//0gw...",
"seed_images": ["https://ocr.space/Content/Images/receipt-ocr-original.webp"],
"prompt_params": {
"language": "English",
"doc_type": "invoices",
"num_solutions": 1,
"enable_handwriting": false,
"output_detail": "dataset"
}
}' \
--output documents.zip \
-D headers.txt
```
**Response:**
- Returns ZIP file immediately (binary)
- Google Drive upload happens in background
- Wait 10-30 seconds, then check status for GDrive URL:
```bash
curl http://localhost:8000/jobs/550e8400-e29b-41d4-a716-446655440000/status
```
**Response (after background upload):**
```json
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"created_at": "2026-03-01T10:00:00Z",
"updated_at": "2026-03-01T10:00:45Z",
"results": {
"download_url": "https://drive.google.com/file/d/abc123xyz/view?usp=sharing",
"file_size_mb": 0.15,
"document_count": 1,
"zip_filename": "docgenie_550e8400-e29b-41d4-a716-446655440000.zip"
}
}
```
---
## Frontend Integration Example
### React + TypeScript
```typescript
import { useState, useEffect } from 'react';
interface JobStatus {
request_id: string;
status: 'queued' | 'processing' | 'generating' | 'completed' | 'failed';
download_url?: string;
error_message?: string;
}
function DocumentGenerator() {
const [jobId, setJobId] = useState<string | null>(null);
const [status, setStatus] = useState<JobStatus | null>(null);
const [googleToken, setGoogleToken] = useState<string>('');
// Step 1: Google OAuth (implement separately)
const handleGoogleAuth = async () => {
// Redirect to Google OAuth
const clientId = 'YOUR_CLIENT_ID';
const redirectUri = 'https://yourapp.com/auth/callback';
const scope = 'https://www.googleapis.com/auth/drive.file';
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
`client_id=${clientId}&` +
`redirect_uri=${redirectUri}&` +
`response_type=code&` +
`scope=${scope}&` +
`access_type=offline&` +
`prompt=consent`;
window.location.href = authUrl;
};
// Step 2: Submit job
const handleGenerateDocuments = async () => {
const response = await fetch('http://localhost:8000/generate/async', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user_id: 1,
google_drive_token: googleToken,
seed_images: ['https://example.com/seed.jpg'],
prompt_params: {
language: 'English',
doc_type: 'receipts',
num_solutions: 3
}
})
});
const job = await response.json();
setJobId(job.request_id);
};
// Step 3: Poll status
useEffect(() => {
if (!jobId) return;
const interval = setInterval(async () => {
const response = await fetch(
`http://localhost:8000/jobs/${jobId}/status`
);
const data = await response.json();
setStatus(data);
if (data.status === 'completed' || data.status === 'failed') {
clearInterval(interval);
}
}, 30000); // Poll every 30 seconds
return () => clearInterval(interval);
}, [jobId]);
return (
<div>
{!googleToken ? (
<button onClick={handleGoogleAuth}>
Connect Google Drive
</button>
) : (
<button onClick={handleGenerateDocuments}>
Generate Documents
</button>
)}
{status && (
<div>
<p>Status: {status.status}</p>
{status.status === 'completed' && (
<a href={status.download_url} target="_blank">
Download Documents
</a>
)}
{status.status === 'failed' && (
<p>Error: {status.error_message}</p>
)}
</div>
)}
</div>
);
}
```
---
## Troubleshooting
### Issue: "google_drive_token is required"
**Cause**: No token provided in request
**Solution**:
```bash
# Make sure you're passing the token
python test_async_api.py --google-token "ya29.a0AfH6SMBx..."
```
### Issue: "Failed to refresh Google Drive token"
**Cause**: Token expired and no refresh token provided
**Solutions**:
1. Get a new token (tokens expire in ~1 hour)
2. Include refresh token in request
3. Frontend should refresh tokens automatically
### Issue: "Google Drive upload failed: insufficient permissions"
**Cause**: Token doesn't have drive.file scope
**Solution**: Re-authorize with correct scope:
```
https://www.googleapis.com/auth/drive.file
```
### Issue: Worker not processing jobs
**Check 1**: Is Redis running?
```bash
redis-cli ping # Should return "PONG"
```
**Check 2**: Is worker running?
```bash
# Check worker logs
journalctl -u docgenie-worker@1 -f
# Or check RQ info
rq info --url redis://localhost:6379/0
```
**Check 3**: Check failed queue
```bash
rq info --queue failed --url redis://localhost:6379/0
```
### Issue: Job stuck in "generating" status
**Cause**: Batch API taking longer than expected
**Solution**: Wait up to 30 minutes for batched requests. Check Anthropic dashboard:
https://console.anthropic.com/settings/batches
### Issue: Cannot access Google Drive link
**Cause**: File not shared properly
**Solution**: Check worker logs for sharing errors. File should have "anyone with link" permission.
---
## Performance Testing
### Test Batch API Cost Savings
```bash
# Generate 10 documents
time python test_async_api.py --google-token TOKEN --num-solutions 10
# Compare with direct API (for reference)
curl -X POST http://localhost:8000/generate \
-H "Content-Type: application/json" \
-d '{"seed_images": ["..."], "prompt_params": {"num_solutions": 10}}'
```
**Expected Results:**
- **Batched API**: 10-20 minutes, ~$2.50 per 1M tokens
- **Direct API**: 3-5 minutes, ~$5.00 per 1M tokens
- **Cost Savings**: 50%
---
## Next Steps
1. βœ… Test locally with script
2. βœ… Verify Google Drive upload
3. βœ… Test with your frontend
4. βœ… Deploy to production (see [DEPLOYMENT.md](DEPLOYMENT.md))
5. βœ… Set up monitoring (see [SCALING.md](SCALING.md))
---
## Additional Resources
- **API Documentation**: http://localhost:8000/docs
- **Deployment Guide**: [DEPLOYMENT.md](DEPLOYMENT.md)
- **Scaling Guide**: [SCALING.md](SCALING.md)
- **Google OAuth Docs**: https://developers.google.com/identity/protocols/oauth2
- **Anthropic Batch API**: https://docs.anthropic.com/en/docs/batch-api