Skip to content

Activity Feed API (FS-9051)

Overview

The Activity Feed endpoint aggregates recent events for a user's saved GPs (watchlist) and provides a unified timeline of important activities like new filings and new funds.

Endpoint

GET /v1/me/activity

Authentication: Required (JWT Bearer token)

Query Parameters

ParameterTypeRequiredDefaultDescription
typesstringNoAll typesComma-separated activity types to include
limitintegerNo50Results per page (max 100)
offsetintegerNo0Pagination offset
sincedatetimeNo30 days agoISO 8601 datetime for filtering events

Supported Activity Types

TypeDescriptionData Source
new_filingNew SEC filings (ADV, Form D)processed_filings table
new_fundNew funds addedprivate_funds table

Response Format

json
{
  "data": [
    {
      "type": "new_filing",
      "crd": "123456",
      "gp_name": "Blackstone Group",
      "event_date": "2025-12-15T10:30:00Z",
      "details": {
        "form_type": "ADV"
      }
    },
    {
      "type": "new_fund",
      "crd": "789012",
      "gp_name": "KKR",
      "event_date": "2025-12-14T15:00:00Z",
      "details": {
        "fund_name": "KKR Global Impact Fund III",
        "fund_id": "805-1234567890"
      }
    }
  ],
  "meta": {
    "total": 127,
    "limit": 50,
    "offset": 0,
    "types_included": ["new_filing", "new_fund"]
  }
}

Event Schema

Base Event Fields

All events have these common fields:

typescript
{
  type: string;        // Event type identifier
  crd: string;         // Adviser CRD number
  gp_name: string;     // GP legal name
  event_date: string;  // ISO 8601 timestamp
  details: object;     // Event-specific data
}

Event-Specific Details

new_filing

typescript
{
  form_type: string;  // "ADV", "ADV/A", or "D"
}

new_fund

typescript
{
  fund_name: string;  // Fund name
  fund_id: string;    // SEC fund ID (805-XXXXXXXXXX)
}

Examples

Get all recent activity (default)

bash
curl -H "Authorization: Bearer $TOKEN" \
  https://api.firmhound.com/v1/me/activity

Filter by activity type

bash
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.firmhound.com/v1/me/activity?types=new_filing"

Multiple activity types

bash
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.firmhound.com/v1/me/activity?types=new_filing,new_fund"

Events from last 7 days

bash
SINCE=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.firmhound.com/v1/me/activity?since=$SINCE"

Pagination

bash
# First page
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.firmhound.com/v1/me/activity?limit=25&offset=0"

# Second page
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.firmhound.com/v1/me/activity?limit=25&offset=25"

Error Responses

400 Bad Request - Invalid activity type

json
{
  "detail": "Invalid activity types: invalid_type. Valid types: new_filing, new_fund"
}

401 Unauthorized - Missing or invalid token

json
{
  "detail": "Could not validate credentials"
}

500 Internal Server Error

json
{
  "detail": "Error fetching activity feed: [error message]"
}

Database Schema

The activity feed queries data from:

processed_filings

sql
CREATE TABLE processed_filings (
    id SERIAL PRIMARY KEY,
    filing_accession VARCHAR(50) UNIQUE NOT NULL,
    crd INTEGER NOT NULL,
    filing_type VARCHAR(20) NOT NULL,
    filing_date DATE NOT NULL,
    status VARCHAR(20) DEFAULT 'pending',
    processed_at TIMESTAMPTZ DEFAULT NOW(),
    created_at TIMESTAMPTZ DEFAULT NOW()
);

private_funds

sql
CREATE TABLE private_funds (
    id SERIAL PRIMARY KEY,
    fund_id VARCHAR(50) NOT NULL,
    adviser_crd VARCHAR(20) NOT NULL,
    name VARCHAR(500),
    created_at TIMESTAMPTZ DEFAULT NOW(),
    -- ... other fields
);

watchlist_items

sql
CREATE TABLE watchlist_items (
    id UUID PRIMARY KEY,
    user_id UUID NOT NULL,
    adviser_crd VARCHAR(20),
    canonical_gp_id INTEGER,
    alert_enabled BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

Implementation Details

Query Strategy

The endpoint uses UNION ALL to combine multiple event sources:

  1. new_filing events: Joins processed_filings with watchlist_items
  2. new_fund events: Joins private_funds with watchlist_items

Performance Considerations

  • Indexes: Ensure indexes exist on:

    • processed_filings.crd
    • processed_filings.filing_date
    • private_funds.adviser_crd
    • private_funds.created_at
    • watchlist_items.user_id
    • watchlist_items.adviser_crd
  • Query Optimization: The UNION query is wrapped in a CTE for efficient sorting and pagination

Future Enhancements

Planned activity types (not yet implemented):

  1. aum_change: AUM changes from adviser_bulk_changes table
  2. search_match: New GPs matching saved searches (requires background job)
  3. fund_close: Fund status changes to "closed" or "liquidating"
  4. personnel_change: New executives/directors from related_persons

Frontend Integration

React Example

typescript
import { useQuery } from '@tanstack/react-query';

interface ActivityEvent {
  type: string;
  crd: string;
  gp_name: string;
  event_date: string;
  details: any;
}

interface ActivityFeedResponse {
  data: ActivityEvent[];
  meta: {
    total: number;
    limit: number;
    offset: number;
    types_included: string[];
  };
}

function useActivityFeed(types?: string[], limit = 50, offset = 0) {
  return useQuery<ActivityFeedResponse>({
    queryKey: ['activity', types, limit, offset],
    queryFn: async () => {
      const params = new URLSearchParams({
        limit: limit.toString(),
        offset: offset.toString(),
      });

      if (types && types.length > 0) {
        params.append('types', types.join(','));
      }

      const response = await fetch(
        `/api/v1/me/activity?${params}`,
        {
          headers: {
            Authorization: `Bearer ${getToken()}`,
          },
        }
      );

      if (!response.ok) throw new Error('Failed to fetch activity feed');
      return response.json();
    },
  });
}

// Usage in component
function ActivityFeed() {
  const { data, isLoading } = useActivityFeed(['new_filing', 'new_fund']);

  if (isLoading) return <div>Loading...</div>;

  return (
    <div className="activity-feed">
      {data?.data.map((event) => (
        <ActivityCard key={`${event.type}-${event.crd}-${event.event_date}`} event={event} />
      ))}
    </div>
  );
}

Testing

See /test_activity_feed.py for comprehensive endpoint tests.

Prerequisites

  1. Create test user and get JWT token
  2. Add some GPs to watchlist
  3. Ensure test data exists in processed_filings and private_funds

Run Tests

bash
# Set your JWT token
export TOKEN="your_jwt_token_here"

# Run test script
python3 test_activity_feed.py

Security Considerations

  1. Authorization: Only returns events for GPs in user's watchlist
  2. Rate Limiting: Subject to tier-based rate limits
  3. Data Exposure: GP names are public data from SEC filings
  4. Performance: Queries limited to max 100 results per request
  • GET /v1/me/watchlist - Manage watchlist
  • GET /v1/me/searches - Manage saved searches
  • GET /v1/gps/{crd} - Get GP details

Changelog

2025-12-15 (FS-9051)

  • Initial implementation with new_filing and new_fund event types
  • Support for filtering by type, pagination, and date range
  • UNION ALL query strategy for aggregating multiple event sources

Firmhound API Documentation