FOIA Dashboard

Manage Freedom of Information Act (FOIA) and public records requests across all 52 US jurisdictions.

Overview

The FOIA Dashboard API provides:

  • Request Management: Create, track, and manage public records requests
  • Multi-Jurisdiction: Support for federal and all 50 state FOIA laws
  • Status Tracking: Monitor request status and agency responses
  • Deadline Monitoring: Automatic deadline calculations and reminders
  • Document Storage: Attach and organize received documents
  • Analytics: Request statistics and response times

Authentication Required

All FOIA Dashboard endpoints require authentication. See Authentication for details.

Creating Requests

Basic FOIA Request

1import { HoleFoundationClient } from '@hole-foundation/sdk';
2
3const client = new HoleFoundationClient({
4 environment: 'https://api.theholefoundation.org',
5 token: 'your-jwt-token'
6});
7
8const request = await client.foiaDashboard.createRequest({
9 jurisdiction: 'federal',
10 agency: 'Department of Justice',
11 subject: 'Freedom of Information Request',
12 description: 'Request for records regarding FOIA processing times from 2023-2024',
13 requester_name: 'John Doe',
14 requester_email: 'john@example.com',
15 delivery_method: 'email'
16});
17
18console.log('Request created:', request.id);
19console.log('Tracking number:', request.tracking_number);
20console.log('Due date:', request.due_date);

Python Example

1from hole_foundation_api import HoleFoundationClient
2
3client = HoleFoundationClient(
4 environment="https://api.theholefoundation.org",
5 token="your-jwt-token"
6)
7
8request = client.foia_dashboard.create_request(
9 jurisdiction="federal",
10 agency="Department of Justice",
11 subject="Freedom of Information Request",
12 description="Request for records regarding FOIA processing times from 2023-2024",
13 requester_name="John Doe",
14 requester_email="john@example.com",
15 delivery_method="email"
16)
17
18print(f"Request created: {request.id}")
19print(f"Tracking number: {request.tracking_number}")
20print(f"Due date: {request.due_date}")

State Public Records Request

1const stateRequest = await client.foiaDashboard.createRequest({
2 jurisdiction: 'california',
3 agency: 'California Department of Transportation',
4 subject: 'Public Records Act Request',
5 description: 'Request for traffic accident reports on Highway 101 from January 2024',
6 requester_name: 'Jane Smith',
7 requester_email: 'jane@example.com',
8 requester_address: '123 Main St, San Francisco, CA 94102',
9 delivery_method: 'postal',
10 expedited: true,
11 expedited_justification: 'Urgent public interest - ongoing litigation'
12});

Request Parameters

Required Fields

  • jurisdiction - State code or “federal” (e.g., “california”, “texas”, “federal”)
  • agency - Name of the government agency
  • subject - Brief subject line for the request
  • description - Detailed description of records requested
  • requester_name - Your full name
  • requester_email - Your email address
  • delivery_method - How you want to receive records (“email”, “postal”, “pickup”)

Optional Fields

1interface CreateRequestOptions {
2 // Requester info
3 requester_organization?: string;
4 requester_phone?: string;
5 requester_address?: string;
6
7 // Request modifiers
8 expedited?: boolean;
9 expedited_justification?: string;
10 fee_waiver_requested?: boolean;
11 fee_waiver_justification?: string;
12
13 // Preferences
14 format_preference?: 'electronic' | 'paper' | 'either';
15 date_range?: {
16 start: string; // ISO date
17 end: string;
18 };
19
20 // Tracking
21 tags?: string[];
22 notes?: string;
23}

Managing Requests

List Your Requests

1const myRequests = await client.foiaDashboard.listRequests({
2 status: 'pending',
3 limit: 20,
4 offset: 0
5});
6
7myRequests.forEach(req => {
8 console.log(`${req.tracking_number} - ${req.agency}`);
9 console.log(` Status: ${req.status}`);
10 console.log(` Submitted: ${req.submitted_date}`);
11 console.log(` Due: ${req.due_date}`);
12});

Filter by Status

1// Get all pending requests
2const pending = await client.foiaDashboard.listRequests({
3 status: 'pending'
4});
5
6// Get completed requests
7const completed = await client.foiaDashboard.listRequests({
8 status: 'completed'
9});
10
11// Get overdue requests
12const overdue = await client.foiaDashboard.listRequests({
13 status: 'overdue'
14});

Available Statuses:

  • draft - Not yet submitted
  • pending - Submitted, awaiting response
  • acknowledged - Agency confirmed receipt
  • processing - Agency is processing
  • partial_response - Some records provided
  • completed - Fully responded
  • denied - Request denied
  • overdue - Past deadline
  • appealed - Appeal filed
  • closed - Request closed

Get Request Details

1const request = await client.foiaDashboard.getRequest(request.id);
2
3console.log('Request Details:');
4console.log(' Agency:', request.agency);
5console.log(' Status:', request.status);
6console.log(' Submitted:', request.submitted_date);
7console.log(' Due:', request.due_date);
8console.log(' Description:', request.description);
9
10if (request.response) {
11 console.log(' Response:', request.response.summary);
12 console.log(' Documents:', request.response.document_count);
13}

Update Request

1const updated = await client.foiaDashboard.updateRequest(request.id, {
2 status: 'processing',
3 notes: 'Agency called to confirm receipt. Processing time estimate: 15 business days.',
4 tags: ['transportation', 'traffic-safety']
5});
6
7console.log('Updated:', updated.status);

Status Workflow

Typical Request Lifecycle

draft
pending (submitted to agency)
acknowledged (agency confirms receipt)
processing (agency working on request)
partial_response OR completed OR denied
[optional] appealed
closed

Handling Status Changes

1async function trackRequest(requestId: string) {
2 const request = await client.foiaDashboard.getRequest(requestId);
3
4 switch (request.status) {
5 case 'pending':
6 // Check if overdue
7 if (new Date() > new Date(request.due_date)) {
8 await client.foiaDashboard.updateRequest(requestId, {
9 status: 'overdue',
10 notes: 'Request is past statutory deadline'
11 });
12 }
13 break;
14
15 case 'denied':
16 // Consider appeal
17 console.log('Request denied. Review for appeal options.');
18 console.log('Reason:', request.denial_reason);
19 break;
20
21 case 'partial_response':
22 // Track what was received
23 console.log('Partial response received');
24 console.log('Documents:', request.response.document_count);
25 console.log('Still awaiting:', request.response.pending_items);
26 break;
27 }
28}

Deadlines and Reminders

Automatic Deadline Calculation

Deadlines are calculated based on jurisdiction-specific laws:

1const request = await client.foiaDashboard.createRequest({
2 jurisdiction: 'federal', // 20 business days
3 agency: 'FBI',
4 subject: 'FOIA Request',
5 // ... other fields
6});
7
8console.log('Due date:', request.due_date); // Auto-calculated
9
10// For California
11const caRequest = await client.foiaDashboard.createRequest({
12 jurisdiction: 'california', // 10 calendar days
13 agency: 'CDPH',
14 // ... other fields
15});
16
17console.log('Due date:', caRequest.due_date); // Different timeline

Check Overdue Requests

1async function findOverdueRequests() {
2 const all = await client.foiaDashboard.listRequests({
3 limit: 1000
4 });
5
6 const now = new Date();
7
8 return all.filter(req =>
9 req.status === 'pending' &&
10 new Date(req.due_date) < now
11 );
12}
13
14const overdue = await findOverdueRequests();
15console.log(`${overdue.length} requests are overdue`);
16
17overdue.forEach(req => {
18 console.log(`${req.agency}: ${req.tracking_number}`);
19 const daysLate = Math.floor(
20 (Date.now() - new Date(req.due_date).getTime()) / (1000 * 60 * 60 * 24)
21 );
22 console.log(` ${daysLate} days overdue`);
23});

Document Management

Attach Documents

1// Upload response document
2await client.foiaDashboard.attachDocument(request.id, {
3 file: responseDocument,
4 type: 'response',
5 description: 'Agency response letter'
6});
7
8// Upload received records
9await client.foiaDashboard.attachDocument(request.id, {
10 file: recordsFile,
11 type: 'records',
12 description: 'Released documents (50 pages)'
13});

List Documents

1const documents = await client.foiaDashboard.listDocuments(request.id);
2
3documents.forEach(doc => {
4 console.log(doc.filename);
5 console.log(` Type: ${doc.type}`);
6 console.log(` Size: ${doc.size_bytes} bytes`);
7 console.log(` Uploaded: ${doc.uploaded_date}`);
8});

Analytics and Reporting

Request Statistics

1async function getStatistics() {
2 const all = await client.foiaDashboard.listRequests({
3 limit: 1000
4 });
5
6 const stats = {
7 total: all.length,
8 by_status: {},
9 by_jurisdiction: {},
10 avg_response_time: 0,
11 completion_rate: 0
12 };
13
14 // Count by status
15 all.forEach(req => {
16 stats.by_status[req.status] = (stats.by_status[req.status] || 0) + 1;
17 stats.by_jurisdiction[req.jurisdiction] =
18 (stats.by_jurisdiction[req.jurisdiction] || 0) + 1;
19 });
20
21 // Calculate completion rate
22 const completed = all.filter(r =>
23 ['completed', 'closed'].includes(r.status)
24 ).length;
25 stats.completion_rate = (completed / all.length) * 100;
26
27 return stats;
28}
29
30const stats = await getStatistics();
31console.log('Request Statistics:', stats);

Agency Response Times

1async function analyzeResponseTimes(jurisdiction: string) {
2 const requests = await client.foiaDashboard.listRequests({
3 jurisdiction,
4 status: 'completed',
5 limit: 1000
6 });
7
8 const responseTimes = requests
9 .filter(r => r.completed_date)
10 .map(r => {
11 const submitted = new Date(r.submitted_date);
12 const completed = new Date(r.completed_date);
13 const days = Math.floor(
14 (completed.getTime() - submitted.getTime()) / (1000 * 60 * 60 * 24)
15 );
16 return { agency: r.agency, days };
17 });
18
19 // Group by agency
20 const byAgency = {};
21 responseTimes.forEach(rt => {
22 if (!byAgency[rt.agency]) {
23 byAgency[rt.agency] = [];
24 }
25 byAgency[rt.agency].push(rt.days);
26 });
27
28 // Calculate averages
29 const averages = Object.entries(byAgency).map(([agency, days]) => ({
30 agency,
31 avg_days: days.reduce((a, b) => a + b, 0) / days.length,
32 count: days.length
33 }));
34
35 return averages.sort((a, b) => b.avg_days - a.avg_days);
36}
37
38const times = await analyzeResponseTimes('federal');
39console.log('Slowest agencies:');
40times.slice(0, 10).forEach(t => {
41 console.log(` ${t.agency}: ${t.avg_days.toFixed(1)} days (${t.count} requests)`);
42});

Best Practices

1. Be Specific

1// ❌ Too vague
2await client.foiaDashboard.createRequest({
3 description: 'All records'
4});
5
6// ✅ Specific
7await client.foiaDashboard.createRequest({
8 description: 'Email correspondence between Director Smith and the Governor\'s office regarding Highway 101 expansion project from January 1, 2024 to March 31, 2024'
9});

2. Use Tags for Organization

1await client.foiaDashboard.updateRequest(request.id, {
2 tags: [
3 'transportation',
4 'highway-101',
5 'q1-2024',
6 'litigation-support'
7 ]
8});
9
10// Later: Find all litigation support requests
11const litigationRequests = (await client.foiaDashboard.listRequests({
12 limit: 1000
13})).filter(r => r.tags?.includes('litigation-support'));

3. Track Communications

1// Document every interaction
2await client.foiaDashboard.updateRequest(request.id, {
3 notes: `
4${new Date().toISOString()}
5Phone call with FOIA officer Jane Smith.
6- Confirmed receipt of request
7- Estimated completion: 15 business days
8- No fees expected
9- Will email when ready
10`
11});

4. Monitor Deadlines Proactively

1// Daily check for approaching deadlines
2async function dailyDeadlineCheck() {
3 const pending = await client.foiaDashboard.listRequests({
4 status: 'pending'
5 });
6
7 const threeDays = 3 * 24 * 60 * 60 * 1000;
8 const soon = pending.filter(req => {
9 const due = new Date(req.due_date).getTime();
10 return due - Date.now() < threeDays;
11 });
12
13 if (soon.length > 0) {
14 console.log(`⚠️ ${soon.length} requests due within 3 days`);
15 soon.forEach(req => {
16 console.log(` ${req.agency}: ${req.tracking_number}`);
17 });
18 }
19}

Error Handling

1import { TheholetruthApiError } from '@hole-foundation/sdk';
2
3try {
4 const request = await client.foiaDashboard.createRequest({
5 jurisdiction: 'federal',
6 // ... other fields
7 });
8} catch (error) {
9 if (error instanceof TheholetruthApiError) {
10 if (error.status === 400) {
11 console.error('Invalid request parameters:', error.message);
12 } else if (error.status === 401) {
13 console.error('Authentication required');
14 } else if (error.status === 429) {
15 console.error('Rate limit exceeded');
16 }
17 }
18 throw error;
19}

Webhook Notifications

Subscribe to request updates:

1// Configure webhook endpoint
2await client.foiaDashboard.configureWebhook({
3 url: 'https://myapp.com/webhooks/foia',
4 events: [
5 'request.status_changed',
6 'request.deadline_approaching',
7 'request.overdue',
8 'document.received'
9 ]
10});
11
12// Your webhook endpoint receives:
13{
14 event: 'request.status_changed',
15 request_id: 'req-123',
16 old_status: 'pending',
17 new_status: 'completed',
18 timestamp: '2024-01-15T10:30:00Z'
19}

Rate Limits

  • Free tier: 50 requests/day
  • Standard tier: 500 requests/day
  • Premium tier: Unlimited

See Rate Limits for details.

Support