Complete API reference for JudgeFinder Platform
Version: 1.0.0
Last Updated: 2025-10-26
Status: Production
Base URL: https://judgefinder.io/api
- API Overview
- Authentication
- Judge Endpoints
- Admin Endpoints
- Advertising Endpoints
- Billing Endpoints
- Other Endpoints
- Error Handling
- Rate Limiting
- Examples
- Production:
https://judgefinder.io/api - Staging:
https://staging.judgefinder.io/api - Development:
http://localhost:3000/api
API versioning strategy:
- v1: Stable API (
/api/v1/*) - Unversioned: Beta endpoints (
/api/*) - Deprecation: 6-month notice before removal
Version Headers:
Accept: application/json
X-API-Version: 1All API responses follow a consistent structure:
Success Response:
{
"data": { ... },
"meta": {
"timestamp": "2025-10-26T10:30:00Z",
"version": "1.0.0"
}
}Paginated Response:
{
"results": [...],
"pagination": {
"page": 1,
"per_page": 20,
"total_count": 1234,
"total_pages": 62
},
"rate_limit_remaining": 95
}Error Response:
{
"error": "Resource not found",
"code": "NOT_FOUND",
"details": {
"resource": "judge",
"id": 123
},
"timestamp": "2025-10-26T10:30:00Z"
}| Code | Meaning | Description |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | Resource created successfully |
| 204 | No Content | Request succeeded, no response body |
| 400 | Bad Request | Invalid request parameters |
| 401 | Unauthorized | Authentication required |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Resource not found |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server error occurred |
| 503 | Service Unavailable | Service temporarily down |
JudgeFinder uses Clerk for authentication with multiple strategies:
- Session Authentication (Browser)
- API Key Authentication (Programmatic)
- Bearer Token Authentication (Mobile apps)
For browser-based requests, Clerk handles authentication automatically via cookies:
// Client-side (automatic)
fetch('/api/judges/123', {
credentials: 'include', // Include session cookie
})For programmatic access:
# Generate API key in dashboard
# /dashboard/api-keys
# Use in requests
curl -H "Authorization: Bearer sk_live_abc123..." \
https://judgefinder.io/api/v1/judges/123API Key Permissions:
read:judges- Read judge dataread:analytics- Read analytics datawrite:reports- Create reportsadmin:all- Full admin access
| User Type | Search Limit | API Limit | Notes |
|---|---|---|---|
| Anonymous | 10/day | N/A | IP-based |
| Authenticated | 100/hour | 1000/hour | User-based |
| Subscriber | 500/hour | 5000/hour | Account-based |
| API Key | N/A | 10000/hour | Key-based |
Required Headers:
Content-Type: application/json
Accept: application/jsonOptional Headers:
Authorization: Bearer {token}
X-API-Key: {api-key}
X-Client-Version: 1.2.3
User-Agent: YourApp/1.0Search for judges by name, jurisdiction, or court.
Endpoint: GET /api/judges/search
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
q |
string | No | Search query (name, court, jurisdiction) |
limit |
integer | No | Results per page (1-500, default: 20) |
page |
integer | No | Page number (default: 1) |
jurisdiction |
string | No | Filter by jurisdiction (e.g., "CA", "NY") |
court_type |
string | No | Filter by court type (e.g., "Superior", "Appellate") |
Example Request:
curl "https://judgefinder.io/api/judges/search?q=smith&limit=10&page=1&jurisdiction=CA"Example Response:
{
"results": [
{
"id": 123,
"name": "Judge John Smith",
"court_name": "Superior Court of California, Los Angeles County",
"jurisdiction": "CA",
"total_cases": 1234,
"slug": "judge-john-smith-123",
"bio": "Judge Smith was appointed in 2015...",
"appointment_date": "2015-06-01"
}
],
"pagination": {
"page": 1,
"per_page": 10,
"total_count": 45,
"total_pages": 5
},
"rate_limit_remaining": 95
}Rate Limit:
- Anonymous: 10 searches/day
- Authenticated: 100 searches/hour
Caching: 60 seconds for search queries, 180 seconds for browse
Retrieve detailed information about a specific judge.
Endpoint: GET /api/v1/judges/{id}
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Judge ID |
Example Request:
curl "https://judgefinder.io/api/v1/judges/123" \
-H "Authorization: Bearer {token}"Example Response:
{
"id": 123,
"name": "Judge John Smith",
"court_name": "Superior Court of California, Los Angeles County",
"jurisdiction": "CA",
"bio": "Judge Smith was appointed...",
"appointment_date": "2015-06-01",
"education": [
{
"degree": "JD",
"institution": "UCLA School of Law",
"year": 2000
}
],
"current_assignment": {
"court_name": "Los Angeles Superior Court",
"department": "Department 42",
"assignment_type": "primary",
"effective_date": "2020-01-01"
},
"analytics": {
"total_cases": 1234,
"settlement_rate": 0.65,
"average_case_duration": 180,
"bias_indicators": {
"consistency_score": 85.5,
"speed_score": 72.3,
"settlement_preference": 15.2
}
},
"sources": {
"primary": "courtlistener",
"verified": true,
"last_updated": "2025-10-20T10:30:00Z"
}
}Rate Limit: 1000 requests/hour (authenticated)
Caching: 300 seconds (5 minutes)
Retrieve analytics for a specific judge.
Endpoint: GET /api/judges/{id}/analytics
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Judge ID |
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
period |
string | No | Time period ("30d", "90d", "1y", "all") |
metrics |
string | No | Comma-separated metrics to include |
Example Request:
curl "https://judgefinder.io/api/judges/123/analytics?period=90d" \
-H "Authorization: Bearer {token}"Example Response:
{
"judge_id": 123,
"period": "90d",
"total_cases": 87,
"outcomes": {
"settled": 42,
"dismissed": 20,
"judgment": 25
},
"settlement_rate": 0.48,
"average_case_duration": 156,
"case_types": [
{
"type": "Civil",
"count": 45,
"settlement_rate": 0.62
},
{
"type": "Criminal",
"count": 30,
"settlement_rate": 0.27
}
],
"bias_indicators": {
"consistency_score": 85.5,
"speed_score": 72.3,
"settlement_preference": 15.2,
"risk_tolerance": 60.1,
"predictability_score": 78.9
},
"temporal_patterns": [
{
"year": 2025,
"month": 7,
"case_count": 32,
"settlement_rate": 0.5
}
]
}Requires: Authentication + Subscription
Rate Limit: 100 requests/hour
Retrieve recent cases for a judge.
Endpoint: GET /api/judges/{id}/recent-cases
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Number of cases (1-50, default: 10) |
offset |
integer | No | Pagination offset |
Example Response:
{
"cases": [
{
"id": 456,
"case_number": "23-CV-12345",
"case_type": "Civil",
"filing_date": "2023-06-15",
"decision_date": "2023-09-20",
"outcome": "Settled",
"parties": ["Smith v. Jones"],
"case_value": 150000
}
],
"total_count": 1234,
"has_more": true
}Get judge by URL-friendly slug.
Endpoint: GET /api/judges/by-slug
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
slug |
string | Yes | Judge slug (e.g., "judge-john-smith-123") |
Example Request:
curl "https://judgefinder.io/api/judges/by-slug?slug=judge-john-smith-123"Example Response:
{
"id": 123,
"name": "Judge John Smith",
"slug": "judge-john-smith-123"
// ... (same as Get Judge by ID)
}Export judge data in CSV or JSON format.
Endpoint: GET /api/v1/judges/export
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
format |
string | No | Export format ("csv" or "json", default: "json") |
jurisdiction |
string | No | Filter by jurisdiction |
limit |
integer | No | Maximum records (1-10000) |
Example Request:
curl "https://judgefinder.io/api/v1/judges/export?format=csv&jurisdiction=CA&limit=100" \
-H "Authorization: Bearer {token}" \
> judges.csvRequires: Subscription
Rate Limit: 10 exports/hour
Admin endpoints require admin authentication.
Check data synchronization status.
Endpoint: GET /api/admin/sync-status
Requires: Admin authentication
Example Response:
{
"last_sync": "2025-10-26T02:00:00Z",
"status": "completed",
"synced_records": {
"judges": 1543,
"courts": 58,
"cases": 45678
},
"errors": [],
"next_scheduled_sync": "2025-10-27T02:00:00Z"
}Retrieve database statistics.
Endpoint: GET /api/admin/stats
Example Response:
{
"judges": {
"total": 1543,
"with_analytics": 1234,
"recently_updated": 87
},
"cases": {
"total": 456789,
"last_30_days": 3456
},
"courts": {
"total": 58,
"active": 54
},
"users": {
"total": 12345,
"active_subscribers": 234,
"trial_users": 45
}
}Manually trigger data synchronization.
Endpoint: POST /api/admin/sync
Request Body:
{
"sync_type": "judges",
"jurisdiction": "CA",
"force": false
}Example Response:
{
"sync_id": "sync_abc123",
"status": "queued",
"estimated_duration": 300,
"message": "Sync job queued successfully"
}Get data quality metrics.
Endpoint: GET /api/admin/data-quality
Example Response:
{
"overall_score": 92.5,
"metrics": {
"completeness": 95.2,
"accuracy": 89.8,
"freshness": 92.5
},
"issues": [
{
"type": "missing_bio",
"count": 87,
"severity": "low"
},
{
"type": "outdated_assignment",
"count": 12,
"severity": "medium"
}
]
}Endpoints for managing advertising campaigns.
Create a new advertising campaign.
Endpoint: POST /api/advertising/campaigns
Request Body:
{
"name": "My Campaign",
"budget": 5000,
"start_date": "2025-11-01",
"end_date": "2025-11-30",
"target_judges": [123, 456, 789],
"ad_creative": {
"headline": "Expert Legal Services",
"description": "Contact us for consultation",
"cta": "Learn More",
"destination_url": "https://example.com"
}
}Example Response:
{
"campaign_id": "camp_abc123",
"status": "pending_approval",
"estimated_impressions": 50000,
"estimated_clicks": 2500,
"cost_per_click": 2.0
}Retrieve campaign performance metrics.
Endpoint: GET /api/advertising/campaigns/{id}/performance
Example Response:
{
"campaign_id": "camp_abc123",
"period": {
"start": "2025-11-01",
"end": "2025-11-30"
},
"metrics": {
"impressions": 45678,
"clicks": 2345,
"click_through_rate": 0.051,
"conversions": 123,
"conversion_rate": 0.052,
"cost": 4690.0,
"cost_per_click": 2.0,
"cost_per_conversion": 38.13
},
"top_performing_judges": [
{
"judge_id": 123,
"impressions": 5678,
"clicks": 345
}
]
}Track when user clicks an ad.
Endpoint: POST /api/advertising/track-click
Request Body:
{
"campaign_id": "camp_abc123",
"judge_id": 123,
"session_id": "sess_xyz789"
}Example Response:
{
"tracked": true,
"redirect_url": "https://example.com?utm_source=judgefinder"
}Endpoints for subscription management and billing.
Retrieve current subscription details.
Endpoint: GET /api/billing/subscription
Requires: Authentication
Example Response:
{
"subscription_id": "sub_abc123",
"status": "active",
"plan": {
"id": "plan_monthly",
"name": "Monthly Universal Access",
"price": 500,
"currency": "USD",
"interval": "month"
},
"current_period": {
"start": "2025-10-01",
"end": "2025-11-01"
},
"cancel_at_period_end": false,
"payment_method": {
"type": "card",
"last4": "4242",
"exp_month": 12,
"exp_year": 2026
}
}Update or change subscription plan.
Endpoint: PUT /api/billing/subscription/update
Request Body:
{
"new_plan_id": "plan_yearly",
"proration_behavior": "create_prorations"
}Example Response:
{
"subscription_id": "sub_abc123",
"status": "updated",
"effective_date": "2025-11-01",
"proration_amount": -417.0,
"new_amount": 5000.0,
"message": "Subscription updated successfully"
}Cancel subscription at end of billing period.
Endpoint: POST /api/billing/subscription/cancel
Request Body:
{
"feedback": "Too expensive",
"cancel_immediately": false
}Example Response:
{
"subscription_id": "sub_abc123",
"status": "canceled",
"cancel_at": "2025-11-01",
"access_until": "2025-11-01",
"refund_amount": 0
}Retrieve billing invoice history.
Endpoint: GET /api/billing/invoices
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | No | Number of invoices (1-100, default: 10) |
starting_after |
string | No | Pagination cursor |
Example Response:
{
"invoices": [
{
"id": "in_abc123",
"number": "INV-2025-001",
"date": "2025-10-01",
"amount_due": 500.0,
"amount_paid": 500.0,
"status": "paid",
"pdf_url": "https://stripe.com/invoices/abc123.pdf"
}
],
"has_more": false
}Create session for Stripe Customer Portal.
Endpoint: POST /api/stripe/create-portal
Example Response:
{
"url": "https://billing.stripe.com/session/abc123"
}Retrieve list of courts.
Endpoint: GET /api/courts
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
jurisdiction |
string | No | Filter by jurisdiction |
type |
string | No | Filter by court type |
Example Response:
{
"courts": [
{
"id": 1,
"name": "Superior Court of California, Los Angeles County",
"jurisdiction": "CA",
"type": "Superior",
"address": "111 N Hill St, Los Angeles, CA 90012",
"judge_count": 234
}
],
"total_count": 58
}Retrieve upcoming judicial elections.
Endpoint: GET /api/elections/upcoming
Example Response:
{
"elections": [
{
"id": 1,
"date": "2026-06-05",
"jurisdiction": "CA",
"position": "Superior Court Judge, Seat 42",
"candidates": [
{
"name": "John Smith",
"party": "Non-partisan",
"incumbent": true
},
{
"name": "Jane Doe",
"party": "Non-partisan",
"incumbent": false
}
]
}
]
}Check API health status.
Endpoint: GET /api/health
Example Response:
{
"status": "healthy",
"version": "1.0.0",
"timestamp": "2025-10-26T10:30:00Z",
"services": {
"database": "healthy",
"cache": "healthy",
"ai": "healthy"
}
}AI-powered conversational judge search.
Endpoint: POST /api/chat
Request Body:
{
"message": "Find judges in Los Angeles who handle civil cases and have high settlement rates",
"context": {
"previous_messages": []
}
}Example Response:
{
"response": "I found 12 judges in Los Angeles Superior Court who specialize in civil cases with settlement rates above 60%. Here are the top 3...",
"judges": [
{
"id": 123,
"name": "Judge John Smith",
"relevance_score": 0.95
}
],
"follow_up_questions": [
"Would you like to see more details about Judge Smith?",
"Should I filter by specific case value ranges?"
]
}{
"error": "Human-readable error message",
"code": "ERROR_CODE",
"details": {
"field": "Additional context"
},
"timestamp": "2025-10-26T10:30:00Z",
"request_id": "req_abc123"
}| Code | HTTP Status | Description |
|---|---|---|
INVALID_REQUEST |
400 | Invalid request parameters |
UNAUTHORIZED |
401 | Authentication required |
FORBIDDEN |
403 | Insufficient permissions |
NOT_FOUND |
404 | Resource not found |
VALIDATION_ERROR |
422 | Request validation failed |
RATE_LIMIT_EXCEEDED |
429 | Too many requests |
INTERNAL_ERROR |
500 | Internal server error |
SERVICE_UNAVAILABLE |
503 | Service temporarily down |
Invalid Request:
{
"error": "Limit cannot exceed 500",
"code": "INVALID_REQUEST",
"details": {
"parameter": "limit",
"provided": 1000,
"maximum": 500
}
}Authentication Required:
{
"error": "Authentication required",
"code": "UNAUTHORIZED",
"details": {
"message": "This endpoint requires authentication. Please include an Authorization header."
}
}Rate Limit Exceeded:
{
"error": "Rate limit exceeded",
"code": "RATE_LIMIT_EXCEEDED",
"details": {
"limit": 100,
"remaining": 0,
"reset": 1730000000,
"reset_in_seconds": 3600
}
}All responses include rate limit information:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1730000000| Endpoint | Anonymous | Authenticated | Subscriber | API Key |
|---|---|---|---|---|
| Search | 10/day | 100/hour | 500/hour | N/A |
| Get Judge | N/A | 1000/hour | 5000/hour | 10000/hour |
| Analytics | N/A | N/A | 100/hour | 1000/hour |
| Export | N/A | N/A | 10/hour | 100/hour |
Best Practices:
- Check
X-RateLimit-Remainingheader - Implement exponential backoff
- Cache responses when possible
- Use webhooks instead of polling
Example Implementation:
async function fetchWithRateLimit(url: string) {
const response = await fetch(url)
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0')
if (remaining < 10) {
console.warn('Approaching rate limit')
}
if (response.status === 429) {
const reset = parseInt(response.headers.get('X-RateLimit-Reset') || '0')
const waitTime = reset - Date.now() / 1000
await sleep(waitTime * 1000)
return fetchWithRateLimit(url) // Retry
}
return response.json()
}Search Judges:
curl -X GET "https://judgefinder.io/api/judges/search?q=smith&jurisdiction=CA" \
-H "Accept: application/json"Get Judge with Authentication:
curl -X GET "https://judgefinder.io/api/v1/judges/123" \
-H "Authorization: Bearer sk_live_abc123..." \
-H "Accept: application/json"Create Campaign:
curl -X POST "https://judgefinder.io/api/advertising/campaigns" \
-H "Authorization: Bearer sk_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"name": "My Campaign",
"budget": 5000,
"target_judges": [123, 456]
}'Using Fetch API:
// Search judges
async function searchJudges(query) {
const response = await fetch(
`https://judgefinder.io/api/judges/search?q=${encodeURIComponent(query)}`,
{
headers: {
Accept: 'application/json',
},
}
)
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`)
}
return await response.json()
}
// Get judge with auth
async function getJudge(id, apiKey) {
const response = await fetch(`https://judgefinder.io/api/v1/judges/${id}`, {
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: 'application/json',
},
})
return await response.json()
}With Type Safety:
interface Judge {
id: number
name: string
court_name: string
jurisdiction: string
total_cases: number
}
interface SearchResponse {
results: Judge[]
pagination: {
page: number
per_page: number
total_count: number
}
rate_limit_remaining: number
}
async function searchJudges(query: string): Promise<SearchResponse> {
const response = await fetch(
`https://judgefinder.io/api/judges/search?q=${encodeURIComponent(query)}`,
{
headers: {
Accept: 'application/json',
},
}
)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
return await response.json()
}Using Requests:
import requests
# Search judges
def search_judges(query, limit=20):
response = requests.get(
'https://judgefinder.io/api/judges/search',
params={'q': query, 'limit': limit},
headers={'Accept': 'application/json'}
)
response.raise_for_status()
return response.json()
# Get judge with authentication
def get_judge(judge_id, api_key):
response = requests.get(
f'https://judgefinder.io/api/v1/judges/{judge_id}',
headers={
'Authorization': f'Bearer {api_key}',
'Accept': 'application/json'
}
)
response.raise_for_status()
return response.json()
# Example usage
results = search_judges('smith', limit=10)
for judge in results['results']:
print(f"{judge['name']} - {judge['court_name']}")Stripe events are received via webhook:
Endpoint: POST /api/webhooks/stripe
Events Handled:
invoice.paid- Invoice successfully paidinvoice.payment_failed- Payment failedcustomer.subscription.created- Subscription createdcustomer.subscription.updated- Subscription updatedcustomer.subscription.deleted- Subscription canceled
Webhook Security:
// Verify webhook signature
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
export async function POST(request: Request) {
const signature = request.headers.get('stripe-signature')!
const body = await request.text()
try {
const event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
)
// Handle event
switch (event.type) {
case 'invoice.paid':
await handleInvoicePaid(event.data.object)
break
// ...
}
return new Response('OK', { status: 200 })
} catch (error) {
return new Response('Webhook signature invalid', { status: 400 })
}
}Endpoint: POST /api/webhooks/courtlistener
Events:
opinion.created- New opinion fileddocket.updated- Docket updatedjudge.updated- Judge information updated
- ARCHITECTURE.md - System architecture
- TESTING.md - Comprehensive testing guide
- DEPLOYMENT.md - Deployment guide
- CONTRIBUTING.md - Development guidelines
Last Updated: 2025-10-26 Maintained By: JudgeFinder Development Team
Need Help? Contact support@judgefinder.io