The FirstQuadrant API provides powerful filtering capabilities to help you find exactly the data you need. This guide covers search queries, advanced filters, and field selection.

Quick start

# Search contacts by name or email
curl "https://api.us.firstquadrant.ai/v5/contacts?query=john" \
  -H "Authorization: Bearer YOUR_API_KEY"

# Filter by email domain
curl "https://api.us.firstquadrant.ai/v5/[email protected]" \
  -H "Authorization: Bearer YOUR_API_KEY"

# Select specific fields
curl "https://api.us.firstquadrant.ai/v5/contacts?select[]=id&select[]=email&select[]=firstName" \
  -H "Authorization: Bearer YOUR_API_KEY"

Search with query parameter

The query parameter performs full-text search across relevant fields:

# Search contacts
GET /v5/contacts?query=john

# Search campaigns
GET /v5/campaigns?query=welcome

# Combine with other filters
GET /v5/contacts?query=john&filter.tags.has=customer

The search is case-insensitive and searches across multiple fields depending on the resource type.

Advanced filtering

Filter syntax

Filters use the format: filter.field.operator=value

# Single filter
filter.email.equals[email protected]

# Multiple filters (AND logic)
filter.firstName.equals=John&filter.lastName.contains=Doe

# Nested field filters
filter.customProperties.industry.equals=technology

Available operators

String operators

OperatorDescriptionExample
equalsExact match (case-sensitive)[email protected]
notNot equal tofilter.status.not=inactive
containsContains substring[email protected]
startsWithStarts with stringfilter.name.startsWith=John
endsWithEnds with stringfilter.email.endsWith=.edu
inValue in arrayfilter.status.in=active,pending
notInValue not in arrayfilter.status.notIn=deleted,archived

Number operators

OperatorDescriptionExample
equalsEqual tofilter.score.equals=100
notNot equal tofilter.score.not=0
gtGreater thanfilter.score.gt=50
gteGreater than or equalfilter.score.gte=50
ltLess thanfilter.score.lt=100
lteLess than or equalfilter.score.lte=100
inValue in arrayfilter.priority.in=1,2,3

Date operators

OperatorDescriptionExample
equalsExact date matchfilter.createdAt.equals=2024-01-15
gtAfter datefilter.createdAt.gt=2024-01-01
gteOn or after datefilter.lastActivityAt.gte=2024-01-01
ltBefore datefilter.createdAt.lt=2024-12-31
lteOn or before datefilter.updatedAt.lte=2024-12-31

Array operators

OperatorDescriptionExample
hasArray contains valuefilter.tags.has=customer
hasEveryArray contains all valuesfilter.tags.hasEvery=customer,vip
hasSomeArray contains any valuefilter.tags.hasSome=lead,prospect
isEmptyArray is emptyfilter.tags.isEmpty=true

Boolean operators

OperatorDescriptionExample
equalsBoolean valuefilter.isActive.equals=true
notOpposite booleanfilter.isVerified.not=true

Filtering examples

// Advanced filtering with multiple conditions
const params = new URLSearchParams({
  "filter.status.equals": "active",
  "filter.createdAt.gte": "2024-01-01",
  "filter.tags.hasSome": "customer,lead",
  "filter.customProperties.score.gt": "50",
  query: "john",
});

const response = await fetch(`https://api.us.firstquadrant.ai/v5/contacts?${params}`, {
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "FirstQuadrant-Organization-ID": "org_YOUR_ORG_ID",
  },
});

const contacts = await response.json();

Field selection

Use the select[] parameter to specify which fields to include in the response:

Basic field selection

# Select only id, email, and name fields
GET /v5/contacts?select[]=id&select[]=email&select[]=firstName&select[]=lastName

# Response includes only selected fields
[
  {
    "id": "con_abc123",
    "email": "[email protected]",
    "firstName": "John",
    "lastName": "Doe"
  }
]

Nested field selection

Use dot notation for nested fields:

# Select nested fields
GET /v5/contacts?select[]=id&select[]=email&select[]=company.name&select[]=customProperties.score

# Response
[
  {
    "id": "con_abc123",
    "email": "[email protected]",
    "company": {
      "name": "Acme Corp"
    },
    "customProperties": {
      "score": 85
    }
  }
]

Performance benefits

Field selection reduces payload size and improves performance:

// Fetch only essential fields for a list view
const listParams = new URLSearchParams({
  "select[]": ["id", "email", "firstName", "lastName", "company.name"],
  limit: "100",
});

// Fetch all fields for a detail view
const detailResponse = await fetch(`https://api.us.firstquadrant.ai/v5/contacts/${contactId}`);

Complex filter combinations

Example 1: Sales qualified leads

Find high-value leads from specific industries:

GET /v5/contacts?
  filter.customProperties.leadScore.gte=80&
  filter.customProperties.industry.in=technology,finance,healthcare&
  filter.tags.has=qualified&
  filter.lastActivityAt.gte=2024-01-01&
  orderBy=customProperties.leadScore&
  sort=desc

Example 2: Email campaign targets

Find contacts for an email campaign:

GET /v5/contacts?
  filter.email.endsWith=.com&
  filter.emailOptOut.equals=false&
  filter.tags.hasEvery=customer,active&
  filter.customProperties.lastPurchaseDate.gte=2023-01-01&
  select[]=id&
  select[]=email&
  select[]=firstName

Example 3: Data cleanup

Find potentially duplicate contacts:

GET /v5/contacts?
  filter.email.contains[email protected]&
  filter.createdAt.gte=2024-01-01&
  orderBy=email&
  sort=asc

Special filters

Null and empty values

# Find contacts without a company
filter.companyId.equals=null

# Find contacts with no tags
filter.tags.isEmpty=true

# Find contacts with any tags
filter.tags.isEmpty=false

Date ranges

// Contacts created this month
const startOfMonth = new Date();
startOfMonth.setDate(1);
startOfMonth.setHours(0, 0, 0, 0);

const params = new URLSearchParams({
  "filter.createdAt.gte": startOfMonth.toISOString(),
  "filter.createdAt.lt": new Date().toISOString(),
});

Pattern matching

# Email domains
filter.email.endsWith[email protected]

# Phone numbers with area code
filter.phone.startsWith=+1415

# Names containing substring
filter.firstName.contains=john

Filter limits and performance

Best practices

  1. Use indexes: Filter on indexed fields (id, email, createdAt) for better performance
  2. Limit results: Always use pagination with filters
  3. Select fields: Only request fields you need
  4. Combine wisely: Too many filters can slow queries

Performance tips

// ❌ Inefficient: Fetching all fields for large dataset
const response = await fetch("/v5/contacts?limit=100");

// ✅ Efficient: Select only needed fields with filters
const response = await fetch(
  "/v5/contacts?" + "filter.status.equals=active&" + "select[]=id&select[]=email&select[]=name&" + "limit=50",
);

Common use cases

1. Segmentation

// VIP customers in California
const vipCA = {
  "filter.tags.has": "vip",
  "filter.customProperties.state.equals": "CA",
  "filter.customProperties.lifetimeValue.gt": "10000",
};

2. Time-based queries

// Contacts inactive for 90 days
const ninetyDaysAgo = new Date();
ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);

const inactive = {
  "filter.lastActivityAt.lt": ninetyDaysAgo.toISOString(),
  "filter.status.equals": "active",
};

3. Data export

// Export specific fields with filters
async function exportContacts(filters) {
  const params = new URLSearchParams({
    ...filters,
    "select[]": ["id", "email", "firstName", "lastName", "createdAt"],
    limit: "100",
  });

  // Paginate through all results
  let allContacts = [];
  let hasMore = true;
  let cursor = null;

  while (hasMore) {
    if (cursor) params.set("startingAfter", cursor);

    const response = await fetch(`/v5/contacts?${params}`);
    const page = await response.json();

    allContacts = allContacts.concat(page);
    hasMore = page.length === 100;

    if (hasMore) cursor = page[page.length - 1].id;
  }

  return allContacts;
}

Troubleshooting

Common issues

  1. Invalid operator: Ensure the operator is valid for the field type
  2. Field not found: Check field names and nested paths
  3. Type mismatch: Ensure filter values match the field type
  4. Special characters: URL-encode special characters in filter values

Debugging tips

// Log the exact URL being called
const params = new URLSearchParams(filters);
console.log(`API URL: https://api.us.firstquadrant.ai/v5/contacts?${params}`);

// Check response headers for debugging info
const response = await fetch(url);
console.log("Request ID:", response.headers.get("X-Request-Id"));