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 FilterPriority
personalAgent's own listingsListAgentMlsIdHighest
officeBrokerage listingsListOfficeKeyHigh
teamTeam member listingsMultiple ListAgentMlsId valuesMedium
searchService area importsCity, subdivision, geoNormal
soldAgent's sold listingsListAgentMlsId with Closed statusNormal
customAd-hoc queriesAny OData filterNormal
demoDemo/sample dataNone (seeded data)Lowest

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, team, search, sold, custom, demo

  -- 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"]
}

Team listings:

{
  "agentMlsIds": ["12345", "67890", "11111"],
  "includeCoListings": true
}

Sold listings:

{
  "agentMlsId": "12345",
  "includeBuyerAgent": true
}

Custom:

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

MLS Numbers:

{
  "mlsNumbers": ["A11234567", "A11234568"]
}

Filter Methods (Dashboard Import Creation)

The dashboard import creation UI supports 4 filter methods:

MethodCategoryDescription
locationsearchFilter by cities and/or postal codes
agentcustomFilter by agent MLS ID (with co-listing option)
officecustomFilter by office/brokerage MLS ID
mlsNumberscustomFilter by specific MLS listing numbers

Quick Create

The quickCreate parameter auto-configures common import types:

ValueWhat It Creates
personalAgent's listings from settings.agent.mlsId
officeOffice listings from settings.brokerage.mlsId
soldAgent's sold listings (status: Closed, includes buyer agent)
teamTeam listings from settings.team.memberMlsIds
// POST /api/dashboard/imports
{ "quickCreate": "personal" }

Safeguards

  • Max 500 listings per agent-created import task
  • Max 10 MLS Search imports per workspace

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. Team Listings (category: 'team') — Optional

    • Filter by team member MLS IDs
    • Requires team member MLS IDs in workspace settings
  4. Sold Listings (category: 'sold') — Optional

    • Filter by agent's MLS ID with Closed status
    • Includes buyer agent matches
  5. 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