Chat API

AI-powered legal research assistant using Claude AI through Cloudflare AI Gateway.

Overview

The Chat API provides:

  • Legal Expertise: Specialized in transparency law, FOIA, and public records
  • Document Analysis: Upload and analyze legal documents
  • FOIA Drafting: Assistance with request drafting
  • Deadline Calculations: Automatic deadline computation
  • Streaming Support: Real-time response streaming
  • Conversation Memory: Multi-turn conversations with context

Features

  • Transparency law Q&A
  • FOIA exemption analysis
  • Statute interpretation
  • Case law research

Document Analysis

  • Review public records requests
  • Analyze government responses
  • Identify potential exemptions

Drafting Assistance

  • FOIA request templates
  • Appeal letter drafting
  • Response letter analysis

Deadline Management

  • Calculate response deadlines
  • Track appeal windows
  • Monitor compliance timelines

Basic Chat

Non-Streaming

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 response = await client.chat({
9 message: 'What is the Freedom of Information Act?',
10 stream: false
11});
12
13console.log(response.message);

Python Example

1from hole_foundation_api import HoleFoundationClient
2
3client = HoleFoundationClient(
4 environment="https://api.theholefoundation.org",
5 token="your-jwt-token"
6)
7
8response = client.chat(
9 message="What is the Freedom of Information Act?",
10 stream=False
11)
12
13print(response.message)

Streaming Responses

For real-time output as the AI generates the response:

TypeScript Streaming

1const stream = await client.chat({
2 message: 'Explain FOIA exemption (b)(6)',
3 stream: true
4});
5
6// Process Server-Sent Events
7const decoder = new TextDecoder();
8
9for await (const chunk of stream) {
10 const text = decoder.decode(chunk);
11 const lines = text.split('\n');
12
13 for (const line of lines) {
14 if (line.startsWith('data: ')) {
15 const data = line.slice(6);
16 if (data === '[DONE]') {
17 console.log('\nStream complete');
18 } else {
19 try {
20 const json = JSON.parse(data);
21 process.stdout.write(json.delta);
22 } catch (e) {
23 // Skip parsing errors
24 }
25 }
26 }
27 }
28}

Python Streaming

1response = client.chat(
2 message="Explain FOIA exemption (b)(6)",
3 stream=True
4)
5
6for chunk in response:
7 print(chunk, end="", flush=True)

React Component

1import { useState } from 'react';
2import { HoleFoundationClient } from '@hole-foundation/sdk';
3
4function ChatInterface() {
5 const [message, setMessage] = useState('');
6 const [response, setResponse] = useState('');
7 const [loading, setLoading] = useState(false);
8
9 const client = new HoleFoundationClient({
10 environment: 'https://api.theholefoundation.org',
11 token: process.env.NEXT_PUBLIC_API_TOKEN
12 });
13
14 const handleSubmit = async () => {
15 setLoading(true);
16 setResponse('');
17
18 const stream = await client.chat({
19 message,
20 stream: true
21 });
22
23 const decoder = new TextDecoder();
24
25 for await (const chunk of stream) {
26 const text = decoder.decode(chunk);
27 const lines = text.split('\n');
28
29 for (const line of lines) {
30 if (line.startsWith('data: ')) {
31 const data = line.slice(6);
32 if (data !== '[DONE]') {
33 try {
34 const json = JSON.parse(data);
35 setResponse(prev => prev + json.delta);
36 } catch (e) {}
37 }
38 }
39 }
40 }
41
42 setLoading(false);
43 };
44
45 return (
46 <div>
47 <textarea
48 value={message}
49 onChange={(e) => setMessage(e.target.value)}
50 placeholder="Ask a legal question..."
51 />
52 <button onClick={handleSubmit} disabled={loading}>
53 {loading ? 'Thinking...' : 'Ask'}
54 </button>
55 <div className="response">
56 {response}
57 </div>
58 </div>
59 );
60}

Contextual Information

Provide Context

Pass context about current project or request:

1const response = await client.chat({
2 message: 'Help me draft a FOIA request',
3 context: {
4 jurisdiction: 'california',
5 agency: 'California Department of Transportation',
6 topic: 'Highway 101 expansion environmental reports',
7 date_range: {
8 start: '2023-01-01',
9 end: '2024-12-31'
10 }
11 }
12});
13
14console.log(response.message);

Continue Conversation

Maintain conversation history with conversation_id:

1// First message
2const first = await client.chat({
3 message: 'What are FOIA exemptions?',
4 stream: false
5});
6
7const conversationId = first.conversation_id;
8console.log(first.message);
9
10// Follow-up using same conversation ID
11const followup = await client.chat({
12 message: 'Which exemption covers privacy?',
13 conversation_id: conversationId,
14 stream: false
15});
16
17console.log(followup.message);

Full Conversation

1async function chat(messages: string[]) {
2 let conversationId: string | undefined;
3
4 for (const message of messages) {
5 const response = await client.chat({
6 message,
7 conversation_id: conversationId,
8 stream: false
9 });
10
11 if (!conversationId) {
12 conversationId = response.conversation_id;
13 }
14
15 console.log(`User: ${message}`);
16 console.log(`AI: ${response.message}\n`);
17 }
18}
19
20await chat([
21 'What is FOIA?',
22 'What are the main exemptions?',
23 'How long do agencies have to respond?',
24 'Can I appeal if denied?'
25]);

Common Use Cases

1const questions = [
2 'What is the difference between FOIA and state public records laws?',
3 'When can agencies charge fees for FOIA requests?',
4 'What constitutes a proper FOIA appeal?',
5 'How do courts interpret exemption (b)(5)?'
6];
7
8for (const question of questions) {
9 const response = await client.chat({
10 message: question,
11 stream: false
12 });
13
14 console.log(`Q: ${question}`);
15 console.log(`A: ${response.message}\n`);
16}

Document Analysis

1const analysis = await client.chat({
2 message: `
3Please analyze this FOIA response for potential issues:
4
5[Agency Response]
6We are denying your request under exemption (b)(6) as the records
7contain personal information. We processed 500 pages and found all
8to be exempt.
9
10Are there any appeal grounds?
11 `,
12 context: {
13 document_type: 'agency_response',
14 jurisdiction: 'federal'
15 }
16});
17
18console.log(analysis.message);

FOIA Request Drafting

1const draft = await client.chat({
2 message: 'Draft a FOIA request for police body camera footage',
3 context: {
4 jurisdiction: 'california',
5 agency: 'San Francisco Police Department',
6 incident_date: '2024-01-15',
7 location: 'Market Street, San Francisco'
8 }
9});
10
11console.log(draft.message);

Deadline Calculation

1const deadline = await client.chat({
2 message: `
3I submitted a FOIA request to the FBI on January 5, 2024.
4When is it due? What if they don't respond?
5 `,
6 context: {
7 jurisdiction: 'federal',
8 agency: 'FBI',
9 submitted_date: '2024-01-05'
10 }
11});
12
13console.log(deadline.message);

Response Format

Non-Streaming Response

1interface ChatResponse {
2 message: string; // AI response text
3 conversation_id: string; // For continuing conversation
4 usage?: {
5 prompt_tokens: number;
6 completion_tokens: number;
7 total_tokens: number;
8 };
9 metadata?: {
10 model: string;
11 finish_reason: string;
12 };
13}

Streaming Response

Server-Sent Events (SSE) format:

data: {"delta": "The ", "conversation_id": "conv-123"}
data: {"delta": "Freedom ", "conversation_id": "conv-123"}
data: {"delta": "of ", "conversation_id": "conv-123"}
data: {"delta": "Information ", "conversation_id": "conv-123"}
data: [DONE]

Advanced Features

Multi-Turn Research Session

1class LegalResearchSession {
2 private conversationId?: string;
3 private client: HoleFoundationClient;
4
5 constructor(client: HoleFoundationClient) {
6 this.client = client;
7 }
8
9 async ask(message: string, context?: any): Promise<string> {
10 const response = await this.client.chat({
11 message,
12 conversation_id: this.conversationId,
13 context,
14 stream: false
15 });
16
17 if (!this.conversationId) {
18 this.conversationId = response.conversation_id;
19 }
20
21 return response.message;
22 }
23
24 async reset() {
25 this.conversationId = undefined;
26 }
27}
28
29// Usage
30const session = new LegalResearchSession(client);
31
32const answer1 = await session.ask('What is FOIA exemption (b)(5)?');
33const answer2 = await session.ask('Give me a case example');
34const answer3 = await session.ask('How would this apply to my situation?', {
35 document_type: 'email',
36 parties: ['agency director', 'legal counsel']
37});

Automated FOIA Workflow

1async function automatedFOIAWorkflow(topic: string, jurisdiction: string) {
2 // Research the law
3 const research = await client.chat({
4 message: `What are the public records laws in ${jurisdiction} regarding ${topic}?`,
5 stream: false
6 });
7
8 // Draft the request
9 const draft = await client.chat({
10 message: `Draft a public records request for ${topic}`,
11 context: { jurisdiction },
12 stream: false
13 });
14
15 // Get deadline info
16 const deadlines = await client.chat({
17 message: 'What are the response deadlines?',
18 context: { jurisdiction },
19 stream: false
20 });
21
22 return {
23 research: research.message,
24 draft: draft.message,
25 deadlines: deadlines.message
26 };
27}
28
29const workflow = await automatedFOIAWorkflow(
30 'police body camera policies',
31 'california'
32);
33
34console.log('Research:', workflow.research);
35console.log('\nDraft:', workflow.draft);
36console.log('\nDeadlines:', workflow.deadlines);

Error Handling

1import { TheholetruthApiError } from '@hole-foundation/sdk';
2
3try {
4 const response = await client.chat({
5 message: 'What is FOIA?',
6 stream: false
7 });
8} catch (error) {
9 if (error instanceof TheholetruthApiError) {
10 if (error.status === 400) {
11 console.error('Invalid message format');
12 } else if (error.status === 401) {
13 console.error('Authentication required');
14 } else if (error.status === 429) {
15 console.error('Rate limit exceeded');
16 } else if (error.status === 503) {
17 console.error('AI Gateway temporarily unavailable');
18 }
19 }
20 throw error;
21}

Best Practices

1. Be Specific

1// ❌ Too vague
2await client.chat({
3 message: 'Tell me about FOIA'
4});
5
6// ✅ Specific question
7await client.chat({
8 message: 'What is the statutory deadline for federal agencies to respond to FOIA requests, and what are the conditions for extending it?'
9});

2. Provide Context

1// ❌ No context
2await client.chat({
3 message: 'Can they deny this?'
4});
5
6// ✅ With context
7await client.chat({
8 message: 'Can the agency deny this request?',
9 context: {
10 request_type: 'email_correspondence',
11 exemption_claimed: '(b)(5) deliberative process',
12 jurisdiction: 'federal'
13 }
14});

3. Use Conversations

1// ❌ Separate requests (loses context)
2await client.chat({ message: 'What is exemption (b)(6)?' });
3await client.chat({ message: 'Give an example' }); // No context
4
5// ✅ Conversation (maintains context)
6const first = await client.chat({
7 message: 'What is exemption (b)(6)?'
8});
9
10await client.chat({
11 message: 'Give an example',
12 conversation_id: first.conversation_id
13});

4. Handle Streaming Gracefully

1async function streamWithFallback(message: string) {
2 try {
3 // Try streaming
4 const stream = await client.chat({
5 message,
6 stream: true
7 });
8
9 // Process stream
10 return await processStream(stream);
11 } catch (error) {
12 // Fallback to non-streaming
13 console.log('Streaming failed, using non-streaming fallback');
14 const response = await client.chat({
15 message,
16 stream: false
17 });
18 return response.message;
19 }
20}

Rate Limits

  • Free tier: 20 chat messages/day
  • Standard tier: 200 messages/day
  • Premium tier: 2,000 messages/day

See Rate Limits for details.

Model Information

  • Model: Claude 3.5 Sonnet (via Cloudflare AI Gateway)
  • Context Window: 200,000 tokens
  • Specialized Training: Transparency law, FOIA, public records
  • Response Time: 1-5 seconds (non-streaming), real-time (streaming)

Support