Skip to content

Filtering Guide

This guide explains how to filter data when making GET requests to the ezyVet API. Filtering allows you to retrieve specific records based on field values and comparison operators.

Overview

The ezyVet API supports field comparators using URL-encoded JSON format. Each filterable field accepts multiple comparators that allow you to perform various types of comparisons.

Key Points:

  • Filters are passed as query parameters
  • Filter values use URL-encoded JSON format: field_name={"comparator":value}
  • Multiple filters can be combined in a single request
  • All filters are case-sensitive
  • Field names must match exactly as documented in the API

Supported Comparators

The following comparators are supported by the ezyVet API:

ComparatorCodeArithmeticSupported TypesDescription
Equaleq=AllExact match
Not equalneq!=AllNot equal to
Greater thangt>Number, Date, DatetimeGreater than
Less thanlt<Number, Date, DatetimeLess than
Greater than or equalgte>=Number, Date, DatetimeGreater than or equal
Less than or equallte<=Number, Date, DatetimeLess than or equal
Likelike-String onlyPattern matching (string fields only)
Inin-String, Number, DateMatch any value in a list

Filter Format

Filters use URL-encoded JSON format. The general structure is:

field_name={"comparator":value}

For the in comparator, use an array:

field_name={"in":[value1,value2,value3]}

For range queries, combine comparators:

field_name={"gte":value1,"lte":value2}

Examples

Large initial syncing

Often there is a need to pull a large number of records from a database as part of an initial load/sync. Due to the way the ezyVet API is built, navigating through thousands of pages can cause extensive load, resulting in a long response time. By reducing the context of the search, this can be mitigated.

It's recommended to partition the sync by following the below steps:

  1. Using the active, id and limit query parameters, pull a certain number of records into context and enlargen the page size by using the gt and lt field comparators.

E.g. Fetch all active contact records where the ID is greater than 0 and less than 1001 and make the page size 200.

HTTP
GET /v2/contact?id={"gt":0, "lt":1001}&active=true&limit=200 HTTP/1.1
Host: api.trial.ezyvet.com
Authorization: Bearer token
  1. With only 1000 objects to parse and 200 objects appearring per page, only 5 pages need to be loaded. By using the page query parameter, we can navigate the data easily and efficiently.
HTTP
GET /v2/contact?id={"gt":0, "lt":1001}&active=true&limit=200&page=1 HTTP/1.1
Host: api.trial.ezyvet.com
Authorization: Bearer token
  1. Once the data from all 5 pages have been loaded, the process can be incremented based on the ID.
HTTP
GET /v2/contact?id={"gt":1000, "lt":2001}&active=true&limit=200&page=1 HTTP/1.1
Host: api.trial.ezyvet.com
Authorization: Bearer token

Basic Examples

1. Equal (eq) - Simple Value

Get all animals with a specific ID:

cURL:

bash
curl --location 'https://api.trial.ezyvet.com/v1/animal?id={"eq":123}' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Python:

python
import json
import urllib.parse
import urllib.request

# Build filter
filter_value = json.dumps({"eq": 123}, separators=(',', ':'))
params = {'id': filter_value}
query_string = urllib.parse.urlencode(params)
url = f"https://api.trial.ezyvet.com/v1/animal?{query_string}"

# Make request
req = urllib.request.Request(url, headers={'Authorization': f'Bearer {token}'})
response = urllib.request.urlopen(req)

TypeScript:

typescript
const filterValue = JSON.stringify({ eq: 123 });
const params = new URLSearchParams({ id: filterValue });
const url = `https://api.trial.ezyvet.com/v1/animal?${params}`;

const response = await fetch(url, {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

2. Greater Than or Equal (gte) - Timestamp Filter

Get animals modified in the last 24 hours:

cURL:

bash
# Calculate timestamp (Unix epoch)
TIMESTAMP=$(date -u -d '24 hours ago' +%s)

curl --location "https://api.trial.ezyvet.com/v1/animal?modified_at={\"gte\":${TIMESTAMP}}" \
  --header 'Authorization: Bearer YOUR_TOKEN'

Python:

python
from datetime import datetime, timedelta
import json
import urllib.parse

# Calculate timestamp for 24 hours ago
twenty_four_hours_ago = datetime.now() - timedelta(hours=24)
timestamp = int(twenty_four_hours_ago.timestamp())

# Build filter
filter_json = json.dumps({"gte": timestamp}, separators=(',', ':'))
params = {'modified_at': filter_json}
query_string = urllib.parse.urlencode(params)
url = f"https://api.trial.ezyvet.com/v1/animal?{query_string}"

TypeScript:

typescript
// Calculate timestamp for 24 hours ago
const twentyFourHoursAgo = new Date();
twentyFourHoursAgo.setHours(twentyFourHoursAgo.getHours() - 24);
const timestamp = Math.floor(twentyFourHoursAgo.getTime() / 1000);

// Build filter
const filterValue = JSON.stringify({ gte: timestamp });
const params = new URLSearchParams({ modified_at: filterValue });
const url = `https://api.trial.ezyvet.com/v1/animal?${params}`;

3. Range Query (gte and lte) - Date Range

Get appointments starting from today through the next 5 days:

cURL:

bash
# Calculate timestamps
TODAY=$(date -u -d 'today 00:00:00' +%s)
FIVE_DAYS=$(date -u -d 'today +5 days 23:59:59' +%s)

curl --location "https://api.trial.ezyvet.com/v1/appointment?start_time={\"gte\":${TODAY},\"lte\":${FIVE_DAYS}}" \
  --header 'Authorization: Bearer YOUR_TOKEN'

Python:

python
from datetime import datetime, timedelta
import json
import urllib.parse

# Calculate timestamps
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
five_days_from_now = today + timedelta(days=5)
five_days_from_now = five_days_from_now.replace(hour=23, minute=59, second=59)

today_timestamp = int(today.timestamp())
five_days_timestamp = int(five_days_from_now.timestamp())

# Build filter with range
start_time_filter = json.dumps(
    {"gte": today_timestamp, "lte": five_days_timestamp},
    separators=(',', ':')
)
params = {'start_time': start_time_filter}
query_string = urllib.parse.urlencode(params)
url = f"https://api.trial.ezyvet.com/v1/appointment?{query_string}"

TypeScript:

typescript
// Calculate timestamps
const today = new Date();
today.setHours(0, 0, 0, 0);
const fiveDaysFromNow = new Date(today);
fiveDaysFromNow.setDate(fiveDaysFromNow.getDate() + 5);
fiveDaysFromNow.setHours(23, 59, 59, 999);

const todayTimestamp = Math.floor(today.getTime() / 1000);
const fiveDaysTimestamp = Math.floor(fiveDaysFromNow.getTime() / 1000);

// Build filter with range
const filterValue = JSON.stringify({ gte: todayTimestamp, lte: fiveDaysTimestamp });
const params = new URLSearchParams({ start_time: filterValue });
const url = `https://api.trial.ezyvet.com/v1/appointment?${params}`;

4. In (in) - Multiple Values

Get appointments with specific appointment type IDs:

cURL:

bash
curl --location 'https://api.trial.ezyvet.com/v1/appointment?appointment_type_id={"in":[1,9,15]}' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Python:

python
import json
import urllib.parse

# List of appointment type IDs
type_ids = [1, 9, 15]

# Build filter using "in" comparator
appointment_type_id_filter = json.dumps({"in": type_ids}, separators=(',', ':'))
params = {'appointment_type_id': appointment_type_id_filter}
query_string = urllib.parse.urlencode(params)
url = f"https://api.trial.ezyvet.com/v1/appointment?{query_string}"

TypeScript:

typescript
// List of appointment type IDs
const typeIds = [1, 9, 15];

// Build filter using "in" comparator
const filterValue = JSON.stringify({ in: typeIds });
const params = new URLSearchParams({ appointment_type_id: filterValue });
const url = `https://api.trial.ezyvet.com/v1/appointment?${params}`;

Combining Multiple Filters

You can combine multiple filters in a single request:

cURL:

bash
curl --location 'https://api.trial.ezyvet.com/v1/appointment?start_time={"gte":1729455014,"lte":1729728000}&appointment_type_id={"in":[1,9,15]}&active=true' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Python:

python
import json
import urllib.parse

# Multiple filters
start_time_filter = json.dumps({"gte": 1729455014, "lte": 1729728000}, separators=(',', ':'))
appointment_type_id_filter = json.dumps({"in": [1, 9, 15]}, separators=(',', ':'))

params = {
    'start_time': start_time_filter,
    'appointment_type_id': appointment_type_id_filter,
    'active': 'true'  # Simple boolean values don't need JSON encoding
}
query_string = urllib.parse.urlencode(params)
url = f"https://api.trial.ezyvet.com/v1/appointment?{query_string}"

TypeScript:

typescript
// Multiple filters
const startTimeFilter = JSON.stringify({ gte: 1729455014, lte: 1729728000 });
const appointmentTypeIdFilter = JSON.stringify({ in: [1, 9, 15] });

const params = new URLSearchParams({
  start_time: startTimeFilter,
  appointment_type_id: appointmentTypeIdFilter,
  active: 'true'  // Simple boolean values don't need JSON encoding
});
const url = `https://api.trial.ezyvet.com/v1/appointment?${params}`;

Advanced Examples

5. Like (like) - Pattern Matching

Search for animals with names containing "Max":

cURL:

bash
curl --location 'https://api.trial.ezyvet.com/v1/animal?name={"like":"%Max%"}' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Python:

python
import json
import urllib.parse

# Pattern matching filter
name_filter = json.dumps({"like": "%Max%"}, separators=(',', ':'))
params = {'name': name_filter}
query_string = urllib.parse.urlencode(params)
url = f"https://api.trial.ezyvet.com/v1/animal?{query_string}"

TypeScript:

typescript
const filterValue = JSON.stringify({ like: "%Max%" });
const params = new URLSearchParams({ name: filterValue });
const url = `https://api.trial.ezyvet.com/v1/animal?${params}`;

TypeScript:

typescript
const filterValue = JSON.stringify({ neq: false });
const params = new URLSearchParams({ active: filterValue });
const url = `https://api.trial.ezyvet.com/v1/animal?${params}`;

Complete Working Examples

Example 1: Get Animals Modified in Last 24 Hours

This example demonstrates filtering by timestamp using the gte comparator.

Python:

python
#!/usr/bin/env python3
import os
import json
import urllib.request
import urllib.parse
from datetime import datetime, timedelta

API_BASE_URL = os.getenv('API_BASE_URL', 'https://api.trial.ezyvet.com')
TOKEN = os.getenv('TOKEN', '')

def get_animals_modified_last_24_hours(token):
    """Get animals modified in the last 24 hours."""
    # Calculate timestamp for 24 hours ago
    twenty_four_hours_ago = datetime.now() - timedelta(hours=24)
    timestamp = int(twenty_four_hours_ago.timestamp())
    
    # Build filter with gte comparator
    filter_json = json.dumps({"gte": timestamp}, separators=(',', ':'))
    params = {'modified_at': filter_json}
    query_string = urllib.parse.urlencode(params)
    api_url = f"{API_BASE_URL}/v1/animal?{query_string}"
    
    headers = {
        'Authorization': f'Bearer {token}',
        'Accept': 'application/json',
    }
    
    req = urllib.request.Request(api_url, headers=headers)
    with urllib.request.urlopen(req) as response:
        data = json.loads(response.read().decode('utf-8'))
        return data.get('items', [])

# Usage
animals = get_animals_modified_last_24_hours(TOKEN)
print(f"Found {len(animals)} animals")

TypeScript:

typescript
async function getAnimalsModifiedLast24Hours(token: string): Promise<any[]> {
  // Calculate timestamp for 24 hours ago
  const twentyFourHoursAgo = new Date();
  twentyFourHoursAgo.setHours(twentyFourHoursAgo.getHours() - 24);
  const timestamp = Math.floor(twentyFourHoursAgo.getTime() / 1000);
  
  // Build filter with gte comparator
  const filterValue = JSON.stringify({ gte: timestamp });
  const params = new URLSearchParams({ modified_at: filterValue });
  const url = `https://api.trial.ezyvet.com/v1/animal?${params}`;
  
  const response = await fetch(url, {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Accept': 'application/json'
    }
  });
  
  const data = await response.json();
  return data.items || [];
}

// Usage
const animals = await getAnimalsModifiedLast24Hours(process.env.TOKEN!);
console.log(`Found ${animals.length} animals`);

Example 2: Get Appointments with Multiple Filters

This example demonstrates combining date range filtering with an "in" filter for appointment types.

Python:

python
#!/usr/bin/env python3
import os
import json
import urllib.request
import urllib.parse
from datetime import datetime, timedelta

API_BASE_URL = os.getenv('API_BASE_URL', 'https://api.trial.ezyvet.com')
TOKEN = os.getenv('TOKEN', '')

def get_appointments_next_5_days(token, type_ids):
    """Get appointments for next 5 days, filtered by appointment type IDs."""
    # Calculate timestamps
    today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
    five_days_from_now = today + timedelta(days=5)
    five_days_from_now = five_days_from_now.replace(hour=23, minute=59, second=59)
    
    today_timestamp = int(today.timestamp())
    five_days_timestamp = int(five_days_from_now.timestamp())
    
    # Build filters
    start_time_filter = json.dumps(
        {"gte": today_timestamp, "lte": five_days_timestamp},
        separators=(',', ':')
    )
    appointment_type_id_filter = json.dumps({"in": type_ids}, separators=(',', ':'))
    
    params = {
        'start_time': start_time_filter,
        'appointment_type_id': appointment_type_id_filter
    }
    query_string = urllib.parse.urlencode(params)
    api_url = f"{API_BASE_URL}/v1/appointment?{query_string}"
    
    headers = {
        'Authorization': f'Bearer {token}',
        'Accept': 'application/json',
    }
    
    req = urllib.request.Request(api_url, headers=headers)
    with urllib.request.urlopen(req) as response:
        data = json.loads(response.read().decode('utf-8'))
        return data.get('items', [])

# Usage
type_ids = [1, 9, 15]  # Consult, Vaccinations, Recheck
appointments = get_appointments_next_5_days(TOKEN, type_ids)
print(f"Found {len(appointments)} appointments")

TypeScript:

typescript
async function getAppointmentsNext5Days(
  token: string,
  typeIds: number[]
): Promise<any[]> {
  // Calculate timestamps
  const today = new Date();
  today.setHours(0, 0, 0, 0);
  const fiveDaysFromNow = new Date(today);
  fiveDaysFromNow.setDate(fiveDaysFromNow.getDate() + 5);
  fiveDaysFromNow.setHours(23, 59, 59, 999);
  
  const todayTimestamp = Math.floor(today.getTime() / 1000);
  const fiveDaysTimestamp = Math.floor(fiveDaysFromNow.getTime() / 1000);
  
  // Build filters
  const startTimeFilter = JSON.stringify({
    gte: todayTimestamp,
    lte: fiveDaysTimestamp
  });
  const appointmentTypeIdFilter = JSON.stringify({ in: typeIds });
  
  const params = new URLSearchParams({
    start_time: startTimeFilter,
    appointment_type_id: appointmentTypeIdFilter
  });
  const url = `https://api.trial.ezyvet.com/v1/appointment?${params}`;
  
  const response = await fetch(url, {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Accept': 'application/json'
    }
  });
  
  const data = await response.json();
  return data.items || [];
}

// Usage
const typeIds = [1, 9, 15];  // Consult, Vaccinations, Recheck
const appointments = await getAppointmentsNext5Days(process.env.TOKEN!, typeIds);
console.log(`Found ${appointments.length} appointments`);

Important Notes

URL Encoding

When building filters, ensure proper URL encoding:

  • Use urllib.parse.urlencode() in Python
  • Use URLSearchParams in TypeScript/JavaScript
  • Use --data-urlencode or proper quoting in cURL

JSON Formatting

  • Use separators=(',', ':') in Python's json.dumps() to avoid spaces
  • Ensure proper JSON formatting (no trailing commas, proper quotes)
  • Arrays for in comparator must be valid JSON arrays: [1,2,3]

Data Types

  • Numbers: Use integers or floats directly: {"gte": 123}
  • Strings: Use quoted strings: {"eq": "value"}
  • Booleans: Use true or false (not quoted): {"eq": true}
  • Timestamps: Use Unix epoch timestamps (seconds since 1970-01-01): {"gte": 1729455014}
  • Null: Use null (not quoted): {"eq": null}

Field Names

  • Field names are case-sensitive
  • Use exact field names as documented in the API
  • Different API versions may use different field names (e.g., start_time vs start_at)

Error Handling

If you use an unsupported filter or incorrect format, the API will return a 400 Bad Request error:

json
{
  "messages": [
    {
      "level": "error",
      "type": "ValidationException",
      "text": "The type id must be a number.",
      "fields": ["type_id"]
    }
  ]
}

Questions

Some API GET endpoints support Questions.

Questions return results from complex queries about a resource. For example, the Product question stocklevel will return various Product stock level numbers, such as total stock, stock allocated to invoices and so on.

WARNING

It's recommended to use the /inventory/v1/products endpoint to retrieve inventory/batch tracked products and their corrosponding inventory balances.

Endpoints with data that seldom changes

The endpoints below represent categories of data where the data rarely changes. It’s encouraged that data retrieved from these endpoints be cached and refreshed semi-regularly. (Usually once daily is sufficient)

  • v1/animalcolour
  • v2/appointmenttype
  • v1/breed
  • v1/contactdetailtype
  • v1/country
  • v1/paymentmethod
  • v1/productgroup
  • v2/resource
  • v2/separation
  • v1/sex
  • v1/species
  • v1/systemsetting
  • v1/tagcategory
  • v2/taxrate
  • v1/user
  • v1/webhookevents

Best Practices

  1. Cache Static Data: For frequently accessed lookup data (like appointment types), cache the results locally
  2. Use Appropriate Comparators: Use gte/lte for ranges, in for multiple values, eq for exact matches
  3. Combine Filters Efficiently: Combine multiple filters in a single request rather than making multiple requests
  4. Handle Pagination: Use limit and page parameters for large result sets
  5. Validate Input: Ensure timestamps and IDs are in the correct format before building filters

Reference