Integration Guide - Phase 2 Features

Version: 2.0 Last Updated: November 28, 2025


Overview

This guide walks you through integrating the Phase 2 multi-file upload and auto-categorization features into your application. Whether you're building a real estate CRM, buyer portal, or agent tool, this guide provides everything you need.


Quick Start

1. Create a Property

Before uploading documents, create a property to group them:

Shell
curl -X POST "https://api.homeinsightai.com/v1/properties" \
  -H "Authorization: Bearer hi_live_abc123xyz..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "listing_456",
    "address": "123 Main St",
    "city": "San Francisco",
    "state": "CA",
    "zip_code": "94102"
  }'

Response:

JSON
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "external_id": "listing_456",
  "address": "123 Main St",
  "city": "San Francisco",
  "state": "CA",
  "zip_code": "94102",
  "document_count": 0,
  "created_at": "2025-11-28T10:00:00Z",
  "updated_at": "2025-11-28T10:00:00Z"
}

2. Upload Multiple Documents

Upload all documents for the property in a single batch:

Shell
curl -X POST "https://api.homeinsightai.com/v1/properties/550e8400-e29b-41d4-a716-446655440000/upload" \
  -H "Authorization: Bearer hi_live_abc123xyz..." \
  -F "files=@Home_Inspection_Report.pdf" \
  -F "files=@Loan_Estimate.pdf" \
  -F "files=@Seller_Disclosure.pdf" \
  -F "files=@Purchase_Agreement.pdf"

Response (instant):

JSON
{
  "property_id": "550e8400-e29b-41d4-a716-446655440000",
  "documents_uploaded": 4,
  "documents_categorized": 4,
  "auto_analyzed": 0,
  "queued_for_analysis": 2,
  "documents": [
    {
      "id": "ana_1a2b3c4d5e6f",
      "filename": "Home_Inspection_Report.pdf",
      "category": "critical",
      "is_analyzed": false,
      "file_size_bytes": 2458923,
      "confidence": 0.95,
      "categorization_method": "filename_pattern",
      "reasoning": "Filename matches critical patterns"
    },
    {
      "id": "ana_2b3c4d5e6f7g",
      "filename": "Loan_Estimate.pdf",
      "category": "important",
      "is_analyzed": false,
      "file_size_bytes": 845672,
      "confidence": 0.90,
      "categorization_method": "filename_pattern",
      "reasoning": "Filename matches important patterns"
    },
    {
      "id": "ana_3c4d5e6f7g8h",
      "filename": "Seller_Disclosure.pdf",
      "category": "critical",
      "is_analyzed": false,
      "file_size_bytes": 1234567,
      "confidence": 0.95,
      "categorization_method": "filename_pattern",
      "reasoning": "Filename matches critical patterns"
    },
    {
      "id": "ana_4d5e6f7g8h9i",
      "filename": "Purchase_Agreement.pdf",
      "category": "optional",
      "is_analyzed": false,
      "file_size_bytes": 987654,
      "confidence": 0.80,
      "categorization_method": "filename_pattern",
      "reasoning": "Filename matches optional patterns"
    }
  ],
  "failed_documents": []
}

Notice:

  • Upload completes instantly (2-5 seconds)
  • Critical/important documents queued for background analysis
  • Optional/noise documents stored as metadata only

3. Chat with Documents

After critical documents complete analysis (~15-30 seconds), you can start asking questions:

Shell
curl -X POST "https://api.homeinsightai.com/v1/chat" \
  -H "Authorization: Bearer hi_live_abc123xyz..." \
  -H "Content-Type: application/json" \
  -d '{
    "query": "What are the major issues found in the inspection?",
    "property_id": "550e8400-e29b-41d4-a716-446655440000"
  }'

Response:

JSON
{
  "answer": "The inspection identified 3 major issues: 1) Roof shingles showing significant wear with curling at edges, estimated 2-3 years remaining life. 2) HVAC system is 18 years old, near end of typical lifespan. 3) Foundation cracks in garage requiring monitoring and potential repair.",
  "citations": [
    {
      "content": "Roof: Composition shingle roof showing wear...",
      "page_number": 9,
      "source_file": "Home_Inspection_Report.pdf",
      "similarity": 0.92
    }
  ],
  "context_used": 3,
  "confidence": 0.88
}

Complete Workflow Examples

React/TypeScript Integration

TypeScript
import React, { useState } from 'react';

interface Document {
  id: string;
  filename: string;
  category: string;
  is_analyzed: boolean;
}

function PropertyDocumentUploader({ propertyId }: { propertyId: string }) {
  const [uploading, setUploading] = useState(false);
  const [documents, setDocuments] = useState<Document[]>([]);

  const handleUpload = async (files: FileList) => {
    setUploading(true);

    const formData = new FormData();
    Array.from(files).forEach(file => {
      formData.append('files', file);
    });

    try {
      const response = await fetch(
        `https://api.homeinsightai.com/v1/properties/${propertyId}/upload`,
        {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.REACT_APP_API_KEY}`,
          },
          body: formData,
        }
      );

      const result = await response.json();

      if (result.failed_documents.length > 0) {
        console.error('Some documents failed:', result.failed_documents);
      }

      setDocuments(result.documents);

      // Show success message
      alert(`Uploaded ${result.documents_uploaded} documents. ${result.queued_for_analysis} queued for analysis.`);

      // Poll for analysis completion
      pollAnalysisStatus(propertyId);

    } catch (error) {
      console.error('Upload failed:', error);
      alert('Upload failed. Please try again.');
    } finally {
      setUploading(false);
    }
  };

  const pollAnalysisStatus = async (propId: string) => {
    const checkStatus = async () => {
      const response = await fetch(
        `https://api.homeinsightai.com/v1/properties/${propId}/documents`,
        {
          headers: {
            'Authorization': `Bearer ${process.env.REACT_APP_API_KEY}`,
          },
        }
      );
      const data = await response.json();

      const allAnalyzed = data.documents.every(
        (doc: any) => doc.status === 'completed' || doc.category === 'optional' || doc.category === 'noise'
      );

      if (allAnalyzed) {
        alert('All critical documents analyzed! Ready to chat.');
        return true;
      }
      return false;
    };

    // Poll every 5 seconds for up to 2 minutes
    const maxAttempts = 24;
    for (let i = 0; i < maxAttempts; i++) {
      await new Promise(resolve => setTimeout(resolve, 5000));
      const done = await checkStatus();
      if (done) break;
    }
  };

  return (
    <div className="uploader">
      <h2>Upload Property Documents</h2>

      <input
        type="file"
        multiple
        accept=".pdf"
        onChange={(e) => e.target.files && handleUpload(e.target.files)}
        disabled={uploading}
      />

      {uploading && <p>Uploading and categorizing documents...</p>}

      {documents.length > 0 && (
        <div className="document-list">
          <h3>Uploaded Documents</h3>
          <ul>
            {documents.map(doc => (
              <li key={doc.id}>
                <strong>{doc.filename}</strong>
                <span className={`badge badge-${doc.category}`}>
                  {doc.category}
                </span>
                {doc.is_analyzed ? (
                  <span className="status-analyzed">✓ Analyzed</span>
                ) : (
                  <span className="status-pending">⏳ Processing...</span>
                )}
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}

export default PropertyDocumentUploader;

Python Integration

Python
import requests
import time
from typing import List
from pathlib import Path

class HomeInsightClient:
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.homeinsightai.com/v1"
        self.headers = {"Authorization": f"Bearer {api_key}"}

    def create_property(self, external_id: str, address: str, **kwargs) -> dict:
        """Create a new property"""
        response = requests.post(
            f"{self.base_url}/properties",
            headers=self.headers,
            json={
                "external_id": external_id,
                "address": address,
                **kwargs
            }
        )
        response.raise_for_status()
        return response.json()

    def upload_documents(self, property_id: str, file_paths: List[str]) -> dict:
        """Upload multiple documents to a property"""
        files = [
            ("files", (Path(fp).name, open(fp, "rb"), "application/pdf"))
            for fp in file_paths
        ]

        response = requests.post(
            f"{self.base_url}/properties/{property_id}/upload",
            headers=self.headers,
            files=files
        )
        response.raise_for_status()
        return response.json()

    def get_property_documents(self, property_id: str) -> dict:
        """Get all documents for a property"""
        response = requests.get(
            f"{self.base_url}/properties/{property_id}/documents",
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()

    def wait_for_analysis(self, property_id: str, timeout: int = 120) -> bool:
        """Wait for all critical documents to complete analysis"""
        start_time = time.time()

        while time.time() - start_time < timeout:
            docs = self.get_property_documents(property_id)

            # Check if all critical/important docs are analyzed
            critical_docs = [
                d for d in docs["documents"]
                if d.get("category") in ("critical", "important")
            ]

            if not critical_docs:
                return True

            analyzed = [
                d for d in critical_docs
                if d.get("status") == "completed"
            ]

            if len(analyzed) == len(critical_docs):
                print(f"All {len(critical_docs)} critical documents analyzed!")
                return True

            print(f"Analysis progress: {len(analyzed)}/{len(critical_docs)} documents")
            time.sleep(5)

        raise TimeoutError(f"Analysis not completed within {timeout} seconds")

    def chat(self, property_id: str, query: str) -> dict:
        """Ask a question about property documents"""
        response = requests.post(
            f"{self.base_url}/chat",
            headers=self.headers,
            json={
                "query": query,
                "property_id": property_id
            }
        )
        response.raise_for_status()
        return response.json()


# Example usage
if __name__ == "__main__":
    client = HomeInsightClient(api_key="hi_live_abc123xyz...")

    # 1. Create property
    property = client.create_property(
        external_id="listing_789",
        address="456 Oak Ave",
        city="Los Angeles",
        state="CA",
        zip_code="90001"
    )
    property_id = property["id"]
    print(f"Created property: {property_id}")

    # 2. Upload documents
    file_paths = [
        "documents/Home_Inspection_Report.pdf",
        "documents/Loan_Estimate.pdf",
        "documents/Seller_Disclosure.pdf",
        "documents/Purchase_Agreement.pdf",
    ]

    result = client.upload_documents(property_id, file_paths)
    print(f"Uploaded {result['documents_uploaded']} documents")
    print(f"Queued for analysis: {result['queued_for_analysis']}")

    # 3. Wait for analysis
    print("Waiting for critical documents to be analyzed...")
    client.wait_for_analysis(property_id)

    # 4. Chat with documents
    answer = client.chat(
        property_id,
        "What are the major issues with the roof?"
    )
    print(f"\nAnswer: {answer['answer']}\n")
    print(f"Citations: {len(answer['citations'])} sources")

Node.js/Express Integration

JavaScript
const express = require('express');
const multer = require('multer');
const FormData = require('form-data');
const axios = require('axios');

const app = express();
const upload = multer({ storage: multer.memoryStorage() });

const API_KEY = process.env.HOME_INSIGHT_API_KEY;
const BASE_URL = 'https://api.homeinsightai.com/v1';

// Create property
app.post('/api/properties', async (req, res) => {
  try {
    const response = await axios.post(
      `${BASE_URL}/properties`,
      req.body,
      {
        headers: {
          'Authorization': `Bearer ${API_KEY}`,
          'Content-Type': 'application/json',
        },
      }
    );
    res.json(response.data);
  } catch (error) {
    res.status(error.response?.status || 500).json({
      error: error.response?.data || error.message,
    });
  }
});

// Upload documents
app.post('/api/properties/:propertyId/upload', upload.array('files'), async (req, res) => {
  try {
    const { propertyId } = req.params;
    const files = req.files;

    if (!files || files.length === 0) {
      return res.status(400).json({ error: 'No files provided' });
    }

    // Create FormData
    const formData = new FormData();
    files.forEach(file => {
      formData.append('files', file.buffer, {
        filename: file.originalname,
        contentType: 'application/pdf',
      });
    });

    // Upload to Home Insight AI
    const response = await axios.post(
      `${BASE_URL}/properties/${propertyId}/upload`,
      formData,
      {
        headers: {
          'Authorization': `Bearer ${API_KEY}`,
          ...formData.getHeaders(),
        },
      }
    );

    res.json(response.data);
  } catch (error) {
    console.error('Upload error:', error.response?.data || error.message);
    res.status(error.response?.status || 500).json({
      error: error.response?.data || error.message,
    });
  }
});

// Get property documents
app.get('/api/properties/:propertyId/documents', async (req, res) => {
  try {
    const { propertyId } = req.params;
    const response = await axios.get(
      `${BASE_URL}/properties/${propertyId}/documents`,
      {
        headers: { 'Authorization': `Bearer ${API_KEY}` },
      }
    );
    res.json(response.data);
  } catch (error) {
    res.status(error.response?.status || 500).json({
      error: error.response?.data || error.message,
    });
  }
});

// Chat endpoint
app.post('/api/chat', async (req, res) => {
  try {
    const response = await axios.post(
      `${BASE_URL}/chat`,
      req.body,
      {
        headers: {
          'Authorization': `Bearer ${API_KEY}`,
          'Content-Type': 'application/json',
        },
      }
    );
    res.json(response.data);
  } catch (error) {
    res.status(error.response?.status || 500).json({
      error: error.response?.data || error.message,
    });
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Advanced Use Cases

1. Pre-Upload from External Storage

If you already have documents in cloud storage (S3, Google Cloud Storage, etc.), you can pass URLs instead of uploading files:

Shell
curl -X POST "https://api.homeinsightai.com/v1/properties/{id}/documents" \
  -H "Authorization: Bearer hi_live_abc123xyz..." \
  -H "Content-Type: application/json" \
  -d '{
    "analysis_id": "ana_existing_123"
  }'

Workflow:

  1. Upload documents to your storage (S3, etc.)
  2. Create analysis via single-file endpoint with pre-signed URL
  3. Add analysis to property
  4. Chat with all property documents

2. Bulk Property Import

Import multiple properties with documents:

Python
def bulk_import_properties(client, properties_data):
    """Import properties with documents in bulk"""
    results = []

    for prop_data in properties_data:
        # Create property
        property = client.create_property(
            external_id=prop_data["external_id"],
            address=prop_data["address"],
            city=prop_data["city"],
            state=prop_data["state"],
            zip_code=prop_data["zip_code"]
        )

        # Upload documents
        if prop_data.get("documents"):
            upload_result = client.upload_documents(
                property["id"],
                prop_data["documents"]
            )
            results.append({
                "property_id": property["id"],
                "address": property["address"],
                "documents_uploaded": upload_result["documents_uploaded"],
                "queued_for_analysis": upload_result["queued_for_analysis"]
            })

    return results

# Example
properties = [
    {
        "external_id": "listing_001",
        "address": "123 Main St",
        "city": "SF",
        "state": "CA",
        "zip_code": "94102",
        "documents": ["docs/listing_001_inspection.pdf", "docs/listing_001_loan.pdf"]
    },
    # ... more properties
]

results = bulk_import_properties(client, properties)
print(f"Imported {len(results)} properties")

3. Categorization Override

If auto-categorization gets it wrong, you can update the category:

Shell
curl -X PATCH "https://api.homeinsightai.com/v1/analyses/{analysis_id}" \
  -H "Authorization: Bearer hi_live_abc123xyz..." \
  -H "Content-Type: application/json" \
  -d '{
    "category": "critical",
    "categorization_metadata": {
      "method": "manual_override",
      "reason": "User reclassified as critical"
    }
  }'

Note: Changing category doesn't automatically trigger analysis. You'll need to manually trigger analysis for newly-critical documents.


4. Progress Tracking with WebSockets

For real-time analysis progress updates:

JavaScript
const ws = new WebSocket('wss://api.homeinsightai.com/ws');

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: 'subscribe',
    property_id: '550e8400-e29b-41d4-a716-446655440000',
    api_key: 'hi_live_abc123xyz...'
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);

  if (data.type === 'analysis_progress') {
    console.log(`Analysis ${data.analysis_id}: ${data.progress}% complete`);
    updateProgressBar(data.analysis_id, data.progress);
  }

  if (data.type === 'analysis_complete') {
    console.log(`Analysis ${data.analysis_id} complete!`);
    enableChatButton();
  }
};

Note: WebSocket support is planned for a future release.


Error Handling

Handle Partial Upload Failures

TypeScript
const handleUpload = async (files: File[]) => {
  const result = await uploadDocuments(propertyId, files);

  // Check for failures
  if (result.failed_documents.length > 0) {
    console.error('Failed documents:', result.failed_documents);

    // Show user which documents failed
    result.failed_documents.forEach(failed => {
      showError(`${failed.filename}: ${failed.error}`);
    });

    // Optionally retry failed uploads
    const failedFiles = files.filter(f =>
      result.failed_documents.some(fd => fd.filename === f.name)
    );

    if (confirm('Retry failed uploads?')) {
      await handleUpload(failedFiles);
    }
  }

  // Process successful uploads
  if (result.documents.length > 0) {
    showSuccess(`${result.documents.length} documents uploaded successfully`);
  }
};

Handle Analysis Timeout

Python
try:
    client.wait_for_analysis(property_id, timeout=120)
except TimeoutError:
    print("Analysis is taking longer than expected.")
    print("You can still chat, but responses may be limited.")

    # Continue anyway
    answer = client.chat(property_id, "What's available so far?")
    print(answer["answer"])

Retry on Rate Limit

JavaScript
async function uploadWithRetry(propertyId, files, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await uploadDocuments(propertyId, files);
    } catch (error) {
      if (error.response?.status === 429 && attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
        console.log(`Rate limited. Retrying in ${delay}ms...`);
        await sleep(delay);
      } else {
        throw error;
      }
    }
  }
}

Testing

Test Data

Use these sample documents for testing:

Shell
# Download sample documents
curl -O https://homeinsightai.com/samples/sample_inspection_report.pdf
curl -O https://homeinsightai.com/samples/sample_loan_estimate.pdf
curl -O https://homeinsightai.com/samples/sample_disclosure.pdf

Test Categorization

Shell
# Test with different filename patterns
test_files=(
  "Home_Inspection_Report_Test.pdf"
  "Loan_Estimate_Test.pdf"
  "Purchase_Agreement_Test.pdf"
  "Receipt_Test.pdf"
  "Generic_Document.pdf"
)

for file in "${test_files[@]}"; do
  echo "Testing: $file"
  curl -X POST "http://localhost:8000/v1/properties/{id}/upload" \
    -H "Authorization: Bearer hi_test_dev_key_12345" \
    -F "files=@$file" \
    | jq '.documents[] | {filename, category, confidence}'
done

Migration from Single-File Upload

Before (Single-File)

JavaScript
// Upload one file at a time
for (const file of files) {
  await uploadDocument(file);  // Sequential, slow
}

After (Multi-File)

JavaScript
// Upload all files at once
await uploadDocuments(propertyId, files);  // Parallel, fast

Migration Checklist:

  • [ ] Create property before uploading documents
  • [ ] Update upload endpoint from /v1/analyses to /v1/properties/{id}/upload
  • [ ] Handle array of documents in response
  • [ ] Handle failed_documents array
  • [ ] Check is_analyzed status before chatting
  • [ ] Update UI to show categorization badges
  • [ ] Test with 5-10 files

Best Practices

  1. Use descriptive filenames: Home_Inspection_Report_123_Main_St.pdf instead of scan.pdf
  2. Batch uploads: Upload all documents at once instead of one-by-one
  3. Handle failures gracefully: Check failed_documents array and retry
  4. Poll for status: Wait for critical documents to complete analysis
  5. Show progress: Display categorization and analysis status to users
  6. Compress PDFs: Reduce file sizes before upload for faster processing

Support

  • Documentation: https://docs.homeinsightai.com
  • API Reference: https://docs.homeinsightai.com/api
  • Discord: https://discord.gg/homeinsightai
  • Email: support@homeinsightai.com
Home Insight AI - Developer Portal