> ## Documentation Index
> Fetch the complete documentation index at: https://docs.firstquadrant.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Filtering & search

> Advanced filtering, search, and field selection for API resources

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

```bash theme={null}
# 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:

```bash theme={null}
# 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`

```bash theme={null}
# 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

| Operator     | Description                  | Example                                |
| ------------ | ---------------------------- | -------------------------------------- |
| `equals`     | Exact match (case-sensitive) | `filter.email.equals=john@example.com` |
| `not`        | Not equal to                 | `filter.status.not=inactive`           |
| `contains`   | Contains substring           | `filter.email.contains=@gmail.com`     |
| `startsWith` | Starts with string           | `filter.name.startsWith=John`          |
| `endsWith`   | Ends with string             | `filter.email.endsWith=.edu`           |
| `in`         | Value in array               | `filter.status.in=active,pending`      |
| `notIn`      | Value not in array           | `filter.status.notIn=deleted,archived` |

#### Number operators

| Operator | Description           | Example                    |
| -------- | --------------------- | -------------------------- |
| `equals` | Equal to              | `filter.score.equals=100`  |
| `not`    | Not equal to          | `filter.score.not=0`       |
| `gt`     | Greater than          | `filter.score.gt=50`       |
| `gte`    | Greater than or equal | `filter.score.gte=50`      |
| `lt`     | Less than             | `filter.score.lt=100`      |
| `lte`    | Less than or equal    | `filter.score.lte=100`     |
| `in`     | Value in array        | `filter.priority.in=1,2,3` |

#### Date operators

| Operator | Description       | Example                                |
| -------- | ----------------- | -------------------------------------- |
| `equals` | Exact date match  | `filter.createdAt.equals=2024-01-15`   |
| `gt`     | After date        | `filter.createdAt.gt=2024-01-01`       |
| `gte`    | On or after date  | `filter.lastActivityAt.gte=2024-01-01` |
| `lt`     | Before date       | `filter.createdAt.lt=2024-12-31`       |
| `lte`    | On or before date | `filter.updatedAt.lte=2024-12-31`      |

#### Array operators

| Operator   | Description               | Example                             |
| ---------- | ------------------------- | ----------------------------------- |
| `has`      | Array contains value      | `filter.tags.has=customer`          |
| `hasEvery` | Array contains all values | `filter.tags.hasEvery=customer,vip` |
| `hasSome`  | Array contains any value  | `filter.tags.hasSome=lead,prospect` |
| `isEmpty`  | Array is empty            | `filter.tags.isEmpty=true`          |

#### Boolean operators

| Operator | Description      | Example                       |
| -------- | ---------------- | ----------------------------- |
| `equals` | Boolean value    | `filter.isActive.equals=true` |
| `not`    | Opposite boolean | `filter.isVerified.not=true`  |

## Filtering examples

<CodeGroup>
  ```javascript JavaScript theme={null}
  // 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();
  ```

  ```python Python theme={null}
  import requests
  from datetime import datetime, timedelta

  # Filter contacts created in the last 30 days with high scores
  thirty_days_ago = (datetime.now() - timedelta(days=30)).isoformat()

  params = {
      'filter.createdAt.gte': thirty_days_ago,
      'filter.customProperties.leadScore.gt': 80,
      'filter.email.endsWith': '.com',
      'orderBy': 'customProperties.leadScore',
      'sort': 'desc',
      'limit': 50
  }

  response = requests.get(
      'https://api.us.firstquadrant.ai/v5/contacts',
      params=params,
      headers={
          'Authorization': 'Bearer YOUR_API_KEY',
          'FirstQuadrant-Organization-ID': 'org_YOUR_ORG_ID'
      }
  )

  high_value_leads = response.json()
  ```

  ```typescript TypeScript theme={null}
  interface ContactFilter {
    email?: {
      equals?: string;
      contains?: string;
      startsWith?: string;
      endsWith?: string;
    };
    tags?: {
      has?: string;
      hasEvery?: string;
      hasSome?: string;
      isEmpty?: boolean;
    };
    createdAt?: {
      gt?: string;
      gte?: string;
      lt?: string;
      lte?: string;
    };
    customProperties?: {
      [key: string]: {
        equals?: any;
        gt?: number;
        contains?: string;
      };
    };
  }

  function buildFilterParams(filters: ContactFilter): URLSearchParams {
    const params = new URLSearchParams();

    Object.entries(filters).forEach(([field, operators]) => {
      Object.entries(operators).forEach(([operator, value]) => {
        if (field === "customProperties") {
          // Handle nested properties
          Object.entries(value).forEach(([prop, propOperators]) => {
            Object.entries(propOperators).forEach(([op, val]) => {
              params.append(`filter.${field}.${prop}.${op}`, String(val));
            });
          });
        } else {
          params.append(`filter.${field}.${operator}`, String(value));
        }
      });
    });

    return params;
  }

  // Usage
  const filters: ContactFilter = {
    email: { endsWith: "@company.com" },
    tags: { hasSome: "customer,lead" },
    createdAt: { gte: "2024-01-01" },
    customProperties: {
      industry: { equals: "technology" },
      employeeCount: { gt: 100 },
    },
  };

  const params = buildFilterParams(filters);
  const response = await fetch(`https://api.us.firstquadrant.ai/v5/contacts?${params}`);
  ```
</CodeGroup>

## Field selection

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

### Basic field selection

```bash theme={null}
# 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:

```bash theme={null}
# 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:

```javascript theme={null}
// 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:

```bash theme={null}
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:

```bash theme={null}
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:

```bash theme={null}
GET /v5/contacts?
  filter.email.contains=@gmail.com&
  filter.createdAt.gte=2024-01-01&
  orderBy=email&
  sort=asc
```

## Special filters

### Null and empty values

```bash theme={null}
# 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

```javascript theme={null}
// 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

```bash theme={null}
# 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

```javascript theme={null}
// ❌ 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

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

### 2. Time-based queries

```javascript theme={null}
// 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

```javascript theme={null}
// 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

```javascript theme={null}
// 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"));
```
