Skip to main content
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/contacts?filter.email.contains=@acme.com" \
  -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=john@example.com

# 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)filter.email.equals=john@example.com
notNot equal tofilter.status.not=inactive
containsContains substringfilter.email.contains=@gmail.com
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": "john@example.com",
    "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": "john@example.com",
    "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=@gmail.com&
  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=@company.com

# 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"));
I