Learn how to filter resources using flexible filter criteria with AND/OR logic.
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.).
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{
"filter": {
"status": { "equalTo": "ACTIVE" },
"createdAt": { "greaterThan": "2025-01-01T00:00:00Z" }
},
"pageSize": 50,
"cursor": null
}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).
Different field types support different filter operations:
For text fields like name, email, phone, notes
| Operation | Description | Example |
|---|---|---|
equalTo | Exact match | { "equalTo": "ABC Corp" } |
notEqualTo | Not equal to | { "notEqualTo": "XYZ Inc" } |
in | Matches any value in array | { "in": ["ABC", "XYZ"] } |
notIn | Does not match any value in array | { "notIn": ["Test", "Demo"] } |
includes | Contains substring (case-insensitive) | { "includes": "warehouse" } |
notIncludes | Does not contain substring | { "notIncludes": "test" } |
startsWith | Starts with prefix (case-insensitive) | { "startsWith": "ABC" } |
notStartsWith | Does not start with prefix | { "notStartsWith": "test" } |
endsWith | Ends with suffix (case-insensitive) | { "endsWith": ".com" } |
notEndsWith | Does not end with suffix | { "notEndsWith": ".test" } |
isNull | Field is null or not null | { "isNull": true } or { "isNull": false } |
Example:
{
"filter": {
"name": { "includes": "warehouse" },
"email": { "endsWith": "@example.com" }
}
}For integer fields like counts, quantities
| Operation | Description | Example |
|---|---|---|
equalTo | Exact match | { "equalTo": 100 } |
notEqualTo | Not equal to | { "notEqualTo": 0 } |
lessThan | Less than | { "lessThan": 50 } |
lessThanOrEqualTo | Less than or equal | { "lessThanOrEqualTo": 100 } |
greaterThan | Greater than | { "greaterThan": 0 } |
greaterThanOrEqualTo | Greater or equal | { "greaterThanOrEqualTo": 10 } |
in | Matches any value | { "in": [1, 2, 3] } |
notIn | Does not match any | { "notIn": [0, -1] } |
isNull | Field is null | { "isNull": false } |
Example:
{
"filter": {
"quantity": { "greaterThan": 0 },
"weight": { "lessThanOrEqualTo": 10000 }
}
}For decimal fields like prices, weights, dimensions
Same operations as IntFilter, but accepts float values:
{
"filter": {
"price": { "greaterThanOrEqualTo": 99.99 },
"weight": { "lessThan": 500.5 }
}
}For true/false fields like isMvmnt, isPrimary
| Operation | Description | Example |
|---|---|---|
equalTo | Exact match | { "equalTo": true } |
notEqualTo | Not equal to | { "notEqualTo": false } |
isNull | Field is null | { "isNull": false } |
Example:
{
"filter": {
"isPrimary": { "equalTo": true },
"isActive": { "notEqualTo": false }
}
}For date/time fields like createdAt, updatedAt, deletedAt
| Operation | Description | Example |
|---|---|---|
equalTo | Exact match | { "equalTo": "2025-01-15T10:00:00Z" } |
notEqualTo | Not equal to | { "notEqualTo": "2025-01-01T00:00:00Z" } |
lessThan | Before datetime | { "lessThan": "2025-02-01T00:00:00Z" } |
lessThanOrEqualTo | On or before | { "lessThanOrEqualTo": "2025-01-31T23:59:59Z" } |
greaterThan | After datetime | { "greaterThan": "2025-01-01T00:00:00Z" } |
greaterThanOrEqualTo | On or after | { "greaterThanOrEqualTo": "2025-01-01T00:00:00Z" } |
isNull | Field 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.
For UUID fields like foreign keys (paymentTermId, deletedById)
| Operation | Description | Example |
|---|---|---|
equalTo | Exact match | { "equalTo": "550e8400-e29b-41d4-a716-446655440000" } |
notEqualTo | Not equal to | { "notEqualTo": "..." } |
in | Matches any UUID | { "in": ["uuid1", "uuid2"] } |
notIn | Does not match any | { "notIn": ["uuid1"] } |
isNull | Field is null | { "isNull": false } |
Example:
{
"filter": {
"paymentTermId": {
"in": [
"550e8400-e29b-41d4-a716-446655440000",
"660e8400-e29b-41d4-a716-446655440001"
]
}
}
}For the primary id field (limited operations)
| Operation | Description | Example |
|---|---|---|
equalTo | Exact match | { "equalTo": "550e8400-..." } |
in | Matches 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.
For the key field (limited operations)
| Operation | Description | Example |
|---|---|---|
equalTo | Exact match | { "equalTo": "ERP-001" } |
in | Matches any key | { "in": ["ERP-001", "ERP-002"] } |
isNull | Field 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
Combine filters using boolean logic:
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.
Use the and operator for clarity or nested conditions:
{
"filter": {
"and": [
{ "status": { "equalTo": "ACTIVE" } },
{ "currency": { "equalTo": "USD" } }
]
}
}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.
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.).
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).
{
"filter": {
"status": { "equalTo": "ACTIVE" },
"createdAt": {
"greaterThanOrEqualTo": "2025-01-01T00:00:00Z"
}
}
}{
"filter": {
"or": [
{ "name": { "includes": "warehouse" } },
{ "email": { "includes": "warehouse" } }
]
}
}{
"filter": {
"and": [
{ "name": { "notIncludes": "test" } },
{ "name": { "notIncludes": "demo" } },
{ "email": { "notEndsWith": ".test" } }
]
}
}{
"filter": {
"paymentTermId": { "isNull": true }
}
}{
"filter": {
"updatedAt": {
"greaterThanOrEqualTo": "2025-01-01T00:00:00Z",
"lessThan": "2025-02-01T00:00:00Z"
}
}
}{
"filter": {
"key": {
"in": [
"ERP-VENDOR-001",
"ERP-VENDOR-002",
"ERP-VENDOR-003"
]
}
}
}{
"filter": {
"id": {
"notIn": [
"550e8400-e29b-41d4-a716-446655440000",
"660e8400-e29b-41d4-a716-446655440001"
]
}
}
}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.
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
}
}
}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
}'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
}'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 } }
]
}
}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',
},
});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'}}
]
}
)- 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
infor multiple values: More efficient than multipleorconditions - 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 use
includeson 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
deletedAtfilter 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/orarrays for clarity
- Use indexed fields first:
id,key,status, timestamps - Limit result set: Use specific filters before pagination
- Use
equalTowhen possible: Faster than substring matches - Avoid
includeson large datasets: Use more specific filters - Leverage caching: Results are internally cached for pagination
// ✅ Efficient - uses indexed fields first
{
"filter": {
"and": [
{ "status": { "equalTo": "ACTIVE" } },
{ "name": { "includes": "warehouse" } }
]
}
}
// ❌ Less efficient - substring search first
{
"filter": {
"name": { "includes": "warehouse" }
}
}Problem: Filter returns empty data array
Possible Causes:
- Filter criteria too restrictive
- All matching records are deleted
- Typo in filter values
- Wrong field name
Solutions:
- Simplify filter criteria progressively
- Override
deletedAtfilter to include deleted records - Verify filter values match expected format
- Check OpenAPI schema for correct field names
Problem: Filter returns more results than expected
Possible Causes:
- Filter too broad
- Case-insensitive matching on
includes - Unexpected null values matching
isNull: true
Solutions:
- Add more specific filter criteria
- Use
equalToinstead ofincludesfor exact matches - Add null checks where appropriate
Problem: Filter appears ignored
Possible Causes:
- Wrong filter operator for field type
- Invalid filter structure
- Field doesn't support that filter type
- Default
deletedAtfilter overriding your filter
Solutions:
- Check field type and use appropriate filter operators
- Validate JSON structure matches examples
- Consult OpenAPI schema for supported filter types
- Explicitly set
deletedAtfilter if needed
Problem: Date filters not matching expected results
Possible Causes:
- Wrong datetime format
- Timezone confusion
- Using
equalToinstead of range operators
Solutions:
- Always use ISO 8601 / RFC 3339 format
- Use UTC timezone (Z suffix)
- Use
greaterThan/lessThanfor date ranges
- Pagination - Paginate through filtered results
- Soft Deletes - Understand how deleted records work
- Client Keys - Filter by your own identifiers
- API Reference - See available filters for each resource type