Skip to content
Last updated

Filtering

Learn how to filter resources using flexible filter criteria with AND/OR logic.

Overview

All MVMNT filter endpoints support powerful filtering capabilities that allow you to query resources using flexible criteria with boolean logic. Filtering is consistent across all resource types (orders, vendors, carriers, customers, etc.).

How Filtering Works

Filter Endpoints

Resources are queried using POST requests to /filter endpoints:

POST /v1/vendors/filter
POST /v1/carriers/filter
POST /v1/customers/filter
POST /v1/orders/filter

Request Structure

{
  "filter": {
    "status": { "equalTo": "ACTIVE" },
    "createdAt": { "greaterThan": "2025-01-01T00:00:00Z" }
  },
  "pageSize": 50,
  "cursor": null
}

Default Behavior

Important: By default, only non-deleted records are returned:

{
  "filter": {
    "deletedAt": { "isNull": true }
  }
}

To include deleted records, explicitly override the deletedAt filter (see Soft Deletes).

Filter Types

Different field types support different filter operations:

StringFilter

For text fields like name, email, phone, notes

OperationDescriptionExample
equalToExact match{ "equalTo": "ABC Corp" }
notEqualToNot equal to{ "notEqualTo": "XYZ Inc" }
inMatches any value in array{ "in": ["ABC", "XYZ"] }
notInDoes not match any value in array{ "notIn": ["Test", "Demo"] }
includesContains substring (case-insensitive){ "includes": "warehouse" }
notIncludesDoes not contain substring{ "notIncludes": "test" }
startsWithStarts with prefix (case-insensitive){ "startsWith": "ABC" }
notStartsWithDoes not start with prefix{ "notStartsWith": "test" }
endsWithEnds with suffix (case-insensitive){ "endsWith": ".com" }
notEndsWithDoes not end with suffix{ "notEndsWith": ".test" }
isNullField is null or not null{ "isNull": true } or { "isNull": false }

Example:

{
  "filter": {
    "name": { "includes": "warehouse" },
    "email": { "endsWith": "@example.com" }
  }
}

IntFilter

For integer fields like counts, quantities

OperationDescriptionExample
equalToExact match{ "equalTo": 100 }
notEqualToNot equal to{ "notEqualTo": 0 }
lessThanLess than{ "lessThan": 50 }
lessThanOrEqualToLess than or equal{ "lessThanOrEqualTo": 100 }
greaterThanGreater than{ "greaterThan": 0 }
greaterThanOrEqualToGreater or equal{ "greaterThanOrEqualTo": 10 }
inMatches any value{ "in": [1, 2, 3] }
notInDoes not match any{ "notIn": [0, -1] }
isNullField is null{ "isNull": false }

Example:

{
  "filter": {
    "quantity": { "greaterThan": 0 },
    "weight": { "lessThanOrEqualTo": 10000 }
  }
}

FloatFilter

For decimal fields like prices, weights, dimensions

Same operations as IntFilter, but accepts float values:

{
  "filter": {
    "price": { "greaterThanOrEqualTo": 99.99 },
    "weight": { "lessThan": 500.5 }
  }
}

BooleanFilter

For true/false fields like isMvmnt, isPrimary

OperationDescriptionExample
equalToExact match{ "equalTo": true }
notEqualToNot equal to{ "notEqualTo": false }
isNullField is null{ "isNull": false }

Example:

{
  "filter": {
    "isPrimary": { "equalTo": true },
    "isActive": { "notEqualTo": false }
  }
}

DatetimeFilter

For date/time fields like createdAt, updatedAt, deletedAt

OperationDescriptionExample
equalToExact match{ "equalTo": "2025-01-15T10:00:00Z" }
notEqualToNot equal to{ "notEqualTo": "2025-01-01T00:00:00Z" }
lessThanBefore datetime{ "lessThan": "2025-02-01T00:00:00Z" }
lessThanOrEqualToOn or before{ "lessThanOrEqualTo": "2025-01-31T23:59:59Z" }
greaterThanAfter datetime{ "greaterThan": "2025-01-01T00:00:00Z" }
greaterThanOrEqualToOn or after{ "greaterThanOrEqualTo": "2025-01-01T00:00:00Z" }
isNullField is null{ "isNull": true }

Example:

{
  "filter": {
    "createdAt": {
      "greaterThanOrEqualTo": "2025-01-01T00:00:00Z",
      "lessThan": "2025-02-01T00:00:00Z"
    }
  }
}

Note: Use ISO 8601 format (RFC 3339) for all datetime values.

UUIDFilter

For UUID fields like foreign keys (paymentTermId, deletedById)

OperationDescriptionExample
equalToExact match{ "equalTo": "550e8400-e29b-41d4-a716-446655440000" }
notEqualToNot equal to{ "notEqualTo": "..." }
inMatches any UUID{ "in": ["uuid1", "uuid2"] }
notInDoes not match any{ "notIn": ["uuid1"] }
isNullField is null{ "isNull": false }

Example:

{
  "filter": {
    "paymentTermId": {
      "in": [
        "550e8400-e29b-41d4-a716-446655440000",
        "660e8400-e29b-41d4-a716-446655440001"
      ]
    }
  }
}

IDFilter

For the primary id field (limited operations)

OperationDescriptionExample
equalToExact match{ "equalTo": "550e8400-..." }
inMatches any UUID{ "in": ["uuid1", "uuid2"] }

Example:

{
  "filter": {
    "id": {
      "in": [
        "550e8400-e29b-41d4-a716-446655440000",
        "660e8400-e29b-41d4-a716-446655440001"
      ]
    }
  }
}

Note: For single ID lookups, use GET /{resource}/{id} instead of filtering.

ClientKeyFilter

For the key field (limited operations)

OperationDescriptionExample
equalToExact match{ "equalTo": "ERP-001" }
inMatches any key{ "in": ["ERP-001", "ERP-002"] }
isNullField is null{ "isNull": false }

Example:

{
  "filter": {
    "key": { "equalTo": "ERP-VENDOR-123" }
  }
}

Note: For single key lookups, use the simpler query parameter: GET /vendors?key=ERP-VENDOR-123

Logical Operators

Combine filters using boolean logic:

AND Logic (Default)

Multiple filters at the same level are implicitly AND-ed:

{
  "filter": {
    "status": { "equalTo": "ACTIVE" },
    "currency": { "equalTo": "USD" }
  }
}

This matches vendors that are both ACTIVE and use USD currency.

Explicit AND

Use the and operator for clarity or nested conditions:

{
  "filter": {
    "and": [
      { "status": { "equalTo": "ACTIVE" } },
      { "currency": { "equalTo": "USD" } }
    ]
  }
}

OR Logic

Use the or operator to match any condition:

{
  "filter": {
    "or": [
      { "status": { "equalTo": "ACTIVE" } },
      { "status": { "equalTo": "PENDING" } }
    ]
  }
}

This matches vendors that are either ACTIVE or PENDING.

NOT Logic

Use the not operator to negate a condition:

{
  "filter": {
    "not": {
      "status": { "equalTo": "INACTIVE" }
    }
  }
}

This matches vendors that are not INACTIVE (includes ACTIVE, PENDING, null, etc.).

Complex Logic

Combine operators for sophisticated queries:

{
  "filter": {
    "and": [
      {
        "or": [
          { "status": { "equalTo": "ACTIVE" } },
          { "status": { "equalTo": "PENDING" } }
        ]
      },
      {
        "or": [
          { "currency": { "equalTo": "USD" } },
          { "currency": { "equalTo": "CAD" } }
        ]
      }
    ]
  }
}

This matches vendors that are (ACTIVE or PENDING) and (USD or CAD).

Common Patterns

Pattern 1: Find Active Records Created Recently

{
  "filter": {
    "status": { "equalTo": "ACTIVE" },
    "createdAt": {
      "greaterThanOrEqualTo": "2025-01-01T00:00:00Z"
    }
  }
}

Pattern 2: Search by Name or Email

{
  "filter": {
    "or": [
      { "name": { "includes": "warehouse" } },
      { "email": { "includes": "warehouse" } }
    ]
  }
}

Pattern 3: Exclude Test/Demo Records

{
  "filter": {
    "and": [
      { "name": { "notIncludes": "test" } },
      { "name": { "notIncludes": "demo" } },
      { "email": { "notEndsWith": ".test" } }
    ]
  }
}

Pattern 4: Find Records Without a Relationship

{
  "filter": {
    "paymentTermId": { "isNull": true }
  }
}

Pattern 5: Find Records Modified in Date Range

{
  "filter": {
    "updatedAt": {
      "greaterThanOrEqualTo": "2025-01-01T00:00:00Z",
      "lessThan": "2025-02-01T00:00:00Z"
    }
  }
}

Pattern 6: Find by Multiple Client Keys

{
  "filter": {
    "key": {
      "in": [
        "ERP-VENDOR-001",
        "ERP-VENDOR-002",
        "ERP-VENDOR-003"
      ]
    }
  }
}

Pattern 7: Exclude Specific IDs

{
  "filter": {
    "id": {
      "notIn": [
        "550e8400-e29b-41d4-a716-446655440000",
        "660e8400-e29b-41d4-a716-446655440001"
      ]
    }
  }
}

Enum Filters

Enum fields (like status, currency, roles) have specialized filters:

{
  "filter": {
    "status": {
      "equalTo": "ACTIVE",
      "in": ["ACTIVE", "PENDING"],
      "notEqualTo": "INACTIVE",
      "notIn": ["INACTIVE", "DELETED"],
      "isNull": false
    }
  }
}

See resource-specific documentation for available enum values.

Array Field Filters

For array fields (like contact roles), use specialized operators:

{
  "filter": {
    "roles": {
      "includes": "BILLING",      // Array contains this value
      "notIncludes": "ARCHIVED",  // Array does not contain this value
      "isNull": false             // Field is not null
    }
  }
}

Complete Examples

Example 1: Find Active Vendors in Specific States

curl -X POST https://api.mvmnt.io/v1/vendors/filter \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {
      "and": [
        { "status": { "equalTo": "ACTIVE" } },
        {
          "or": [
            { "corporateAddress": { "state": { "equalTo": "CA" } } },
            { "corporateAddress": { "state": { "equalTo": "TX" } } }
          ]
        }
      ]
    },
    "pageSize": 100
  }'

Example 2: Find Contacts Created Last 30 Days

curl -X POST https://api.mvmnt.io/v1/vendor-contacts/filter \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {
      "createdAt": {
        "greaterThanOrEqualTo": "2025-01-15T00:00:00Z"
      }
    },
    "pageSize": 50
  }'

Example 3: Complex Business Query

Find vendors that:

  • Are ACTIVE or PENDING
  • Use USD or CAD currency
  • Were created in 2025
  • Are not test vendors
  • Have a payment term set
{
  "filter": {
    "and": [
      {
        "or": [
          { "status": { "equalTo": "ACTIVE" } },
          { "status": { "equalTo": "PENDING" } }
        ]
      },
      {
        "or": [
          { "currency": { "equalTo": "USD" } },
          { "currency": { "equalTo": "CAD" } }
        ]
      },
      {
        "createdAt": {
          "greaterThanOrEqualTo": "2025-01-01T00:00:00Z",
          "lessThan": "2026-01-01T00:00:00Z"
        }
      },
      { "name": { "notIncludes": "test" } },
      { "paymentTermId": { "isNull": false } }
    ]
  }
}

Example 4: JavaScript Helper

async function filterVendors(criteria) {
  const response = await fetch('https://api.mvmnt.io/v1/vendors/filter', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      filter: criteria,
      pageSize: 250,
    }),
  });

  return response.json();
}

// Find active USD vendors
const activeUsdVendors = await filterVendors({
  and: [
    { status: { equalTo: 'ACTIVE' } },
    { currency: { equalTo: 'USD' } },
  ],
});

// Find vendors by name pattern
const warehouseVendors = await filterVendors({
  name: { includes: 'warehouse' },
});

// Find recently created vendors
const recentVendors = await filterVendors({
  createdAt: {
    greaterThanOrEqualTo: '2025-01-01T00:00:00Z',
  },
});

Example 5: Python Helper

import requests
from typing import Dict, List
from datetime import datetime, timedelta

def filter_vendors(
    access_token: str,
    filter_criteria: Dict,
    page_size: int = 250
) -> List[Dict]:
    """Filter vendors using given criteria"""
    response = requests.post(
        'https://api.mvmnt.io/v1/vendors/filter',
        headers={
            'Authorization': f'Bearer {access_token}',
            'Content-Type': 'application/json',
        },
        json={
            'filter': filter_criteria,
            'pageSize': page_size,
        }
    )
    response.raise_for_status()

    result = response.json()
    return result['data']

# Find active vendors
active_vendors = filter_vendors(
    token,
    {'status': {'equalTo': 'ACTIVE'}}
)

# Find vendors created in last 7 days
week_ago = (datetime.utcnow() - timedelta(days=7)).isoformat() + 'Z'
recent_vendors = filter_vendors(
    token,
    {'createdAt': {'greaterThanOrEqualTo': week_ago}}
)

# Complex query
complex_result = filter_vendors(
    token,
    {
        'and': [
            {'status': {'equalTo': 'ACTIVE'}},
            {'currency': {'in': ['USD', 'CAD']}},
            {'name': {'notIncludes': 'test'}}
        ]
    }
)

Best Practices

✅ Do

  • Use specific filters: More specific filters are more efficient
  • Filter on indexed fields: id, key, status, timestamps are indexed
  • Combine with pagination: Always use with proper pagination for large results
  • Use in for multiple values: More efficient than multiple or conditions
  • Leverage default deletedAt filter: Let the API filter deleted records automatically
  • Use ISO 8601 for dates: Always use RFC 3339 format for datetime fields

❌ Don't

  • Don't use includes on large text fields: Can be slow, prefer exact matches when possible
  • Don't create overly complex filters: Break into multiple simpler queries if needed
  • Don't filter when you can use direct lookup: Use GET /{id} or ?key= for single records
  • Don't forget about deleted records: Override deletedAt filter if you need them
  • Don't use filters for counting: Use dedicated count/stats endpoints when available
  • Don't mix AND/OR at the same level: Use explicit and/or arrays for clarity

Performance Tips

Optimize for Speed

  1. Use indexed fields first: id, key, status, timestamps
  2. Limit result set: Use specific filters before pagination
  3. Use equalTo when possible: Faster than substring matches
  4. Avoid includes on large datasets: Use more specific filters
  5. Leverage caching: Results are internally cached for pagination

Query Efficiency

// ✅ Efficient - uses indexed fields first
{
  "filter": {
    "and": [
      { "status": { "equalTo": "ACTIVE" } },
      { "name": { "includes": "warehouse" } }
    ]
  }
}

// ❌ Less efficient - substring search first
{
  "filter": {
    "name": { "includes": "warehouse" }
  }
}

Troubleshooting

No Results Returned

Problem: Filter returns empty data array

Possible Causes:

  1. Filter criteria too restrictive
  2. All matching records are deleted
  3. Typo in filter values
  4. Wrong field name

Solutions:

  1. Simplify filter criteria progressively
  2. Override deletedAt filter to include deleted records
  3. Verify filter values match expected format
  4. Check OpenAPI schema for correct field names

Too Many Results

Problem: Filter returns more results than expected

Possible Causes:

  1. Filter too broad
  2. Case-insensitive matching on includes
  3. Unexpected null values matching isNull: true

Solutions:

  1. Add more specific filter criteria
  2. Use equalTo instead of includes for exact matches
  3. Add null checks where appropriate

Filter Not Working

Problem: Filter appears ignored

Possible Causes:

  1. Wrong filter operator for field type
  2. Invalid filter structure
  3. Field doesn't support that filter type
  4. Default deletedAt filter overriding your filter

Solutions:

  1. Check field type and use appropriate filter operators
  2. Validate JSON structure matches examples
  3. Consult OpenAPI schema for supported filter types
  4. Explicitly set deletedAt filter if needed

Date Filter Issues

Problem: Date filters not matching expected results

Possible Causes:

  1. Wrong datetime format
  2. Timezone confusion
  3. Using equalTo instead of range operators

Solutions:

  1. Always use ISO 8601 / RFC 3339 format
  2. Use UTC timezone (Z suffix)
  3. Use greaterThan/lessThan for date ranges

Next Steps