1 OAK MLS

Import Tasks

Configurable listing import tasks with categories for filtering by source

Import Tasks

Import tasks define what listings to sync from the MLS. Each task has a category that determines how listings are filtered and displayed.

Overview

Import tasks replace the original single-sync model with flexible, categorized imports:

  • Agent's personal listings from their MLS ID
  • Office/brokerage listings from office MLS ID
  • Geographic search imports from service areas
  • Custom queries for specific criteria

Task Categories

CategoryPurposeTypical Filter
personalAgent's own listingsListAgentMlsId
officeBrokerage listingsListOfficeKey
searchService area importsCity, subdivision, geo
customAd-hoc queriesAny OData filter

Database Schema

import_tasks Table

CREATE TABLE import_tasks (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  workspace_id UUID NOT NULL REFERENCES workspaces(id),
  mls_connection_id UUID NOT NULL REFERENCES mls_connections(id),

  -- Task identity
  name TEXT NOT NULL,
  description TEXT,
  category TEXT DEFAULT 'personal', -- personal, office, search, custom

  -- MLS filtering
  filter_config JSONB DEFAULT '{}',
  status_filters TEXT[] DEFAULT ARRAY['Active'],
  price_min INTEGER,
  price_max INTEGER,
  max_import_limit INTEGER,

  -- Sync configuration
  field_mapping_id UUID REFERENCES field_mappings(id),
  sync_cadence TEXT DEFAULT 'manual', -- manual, hourly, daily
  is_enabled BOOLEAN DEFAULT true,
  is_agent_visible BOOLEAN DEFAULT true,

  -- Sync state
  last_synced_at TIMESTAMPTZ,
  last_sync_cursor TEXT,

  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

Filter Config Structure

The filter_config JSONB field varies by category:

Personal listings:

{
  "agentMlsId": "12345"
}

Office listings:

{
  "officeMlsId": "BROKER123"
}

Search (service area):

{
  "serviceAreaId": "abc-123",
  "cities": ["miami"],
  "subdivisions": ["coconut-grove"]
}

Custom:

{
  "customFilter": "City eq 'Miami' and ListPrice gt 1000000"
}

Listing Source Tracking

import_task_id in Listings

Each synced listing stores its import task reference:

ALTER TABLE listings
ADD COLUMN import_task_id UUID REFERENCES import_tasks(id);

import_category in Typesense

Listings are indexed with their category for filtering:

{
  "id": "listing-uuid",
  "import_category": "personal",
  ...
}

Listing Source Filtering

The client site can filter listings by source:

SourceTypesense FilterUse Case
agentimport_category:=[personal,office]"My Listings" page
allNo filterBrowse all available listings

Search Function

interface SearchFilters {
  listingSource?: 'agent' | 'all';
  // ... other filters
}

function buildFilters(filters: SearchFilters): string {
  const parts = [];

  if (filters.listingSource === 'agent') {
    parts.push(`import_category:=[personal,office]`);
  }
  // 'all' includes everything

  return parts.join(' && ');
}

API Endpoints

Dashboard API (Agent)

GET /api/dashboard/imports

List import tasks visible to the agent.

Response:

{
  "imports": [
    {
      "id": "task-uuid",
      "name": "My Listings",
      "description": "Personal listings from MLS",
      "category": "personal",
      "filter_config": { "agentMlsId": "12345" },
      "status_filters": ["Active", "Pending"],
      "sync_cadence": "daily",
      "is_enabled": true,
      "last_synced_at": "2024-01-15T10:30:00Z",
      "listing_count": 12,
      "latest_sync_run": {
        "id": "run-uuid",
        "status": "success",
        "started_at": "2024-01-15T10:30:00Z",
        "stats": { "fetched": 15, "upserted": 12 }
      }
    }
  ]
}

POST /api/dashboard/imports/[id]/sync

Trigger a manual sync for an import task.

Admin API

GET /api/admin/workspaces/[id]/imports

List all import tasks for a workspace.

POST /api/admin/workspaces/[id]/imports

Create a new import task.

Request body:

{
  "name": "Miami Beach Listings",
  "category": "search",
  "filter_config": {
    "cities": ["miami-beach"]
  },
  "status_filters": ["Active"],
  "max_import_limit": 500,
  "sync_cadence": "daily"
}

PATCH /api/admin/workspaces/[id]/imports/[taskId]

Update an import task.

POST /api/admin/workspaces/[id]/imports/[taskId]/toggle

Enable/disable an import task.

POST /api/admin/workspaces/[id]/imports/[taskId]/run

Trigger a sync run.

Sync Runs

Each sync execution is logged:

sync_runs Table

CREATE TABLE sync_runs (
  id UUID PRIMARY KEY,
  workspace_id UUID NOT NULL REFERENCES workspaces(id),
  mls_connection_id UUID REFERENCES mls_connections(id),
  import_task_id UUID REFERENCES import_tasks(id),

  started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  finished_at TIMESTAMPTZ,
  status TEXT NOT NULL DEFAULT 'running', -- running, success, error

  stats JSONB, -- { fetched, upserted, indexed, errors }
  error TEXT
);

Stats Structure

{
  "fetched": 150,
  "upserted": 148,
  "indexed": 148,
  "errors": 2,
  "duration_ms": 45000
}

Creating from Service Areas

Import tasks can be auto-generated from service areas:

POST /api/dashboard/imports/from-service-areas

{
  "serviceAreaIds": ["area-1", "area-2"]
}

This creates tasks with:

  • category: 'search'
  • name: 'Service Area: {area.name}'
  • Filter config from the area definition
  • is_agent_visible: true

Default Task Setup

When onboarding a new workspace, create default tasks:

  1. Personal Listings (category: 'personal')

    • Filter by agent's MLS ID
    • High priority, frequent sync
  2. Office Listings (category: 'office')

    • Filter by brokerage MLS ID
    • Medium priority
  3. Service Area Imports (category: 'search')

    • Created from defined service areas
    • Bulk listing import

Best Practices

  1. Start with personal listings — Agent's own listings should always be up-to-date
  2. Use max_import_limit — Prevent runaway imports for broad searches
  3. Set appropriate cadences — Personal: hourly, Search: daily
  4. Monitor sync runs — Check for errors and failed imports
  5. Use status_filters wisely — Usually just "Active" for public display

On this page