Documentation Index Fetch the complete documentation index at: https://docs.grainql.com/llms.txt
Use this file to discover all available pages before exploring further.
Endpoint
POST /v1/api/query/count/{tenantId}
Get the count of events matching your filters. This endpoint is optimized for aggregations and is much faster than fetching all events just to count them.
Parameters
Path Parameters
Parameter Type Description tenantIdstring Your tenant identifier (use the alias shown on your dashboard)
Request Body
Field Type Required Description eventstring No Filter by specific event name afterstring No Start date (YYYY-MM-DD format) beforestring No End date (YYYY-MM-DD format) filterSetarray No Array of filter objects
Filter Object
Field Type Required Description propertystring Yes Property path to filter on comparisonstring Yes Comparison operator valueany Yes Value to compare against
Response
Returns a simple count object:
When to Use Count vs Query
✅ Use Count Endpoint For:
Aggregations : Getting totals, averages, or other metrics
Dashboard widgets : Displaying numbers without needing event details
Performance monitoring : Checking if thresholds are exceeded
Quick checks : Verifying if events exist without fetching them
❌ Use Query Endpoint For:
Event details : When you need the actual event data
User journeys : Analyzing sequences of events
Data export : When you need to process individual events
Debugging : Investigating specific event properties
Examples
Basic Count
Count all page views in a date range:
curl -X POST https://queryapis.grainql.com/v1/api/query/count/your-tenant-id \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_SECRET_KEY" \
-d '{
"event": "page_viewed",
"after": "2024-01-01",
"before": "2024-01-31"
}'
Response:
Filtered Count
Count high-value purchases:
curl -X POST https://queryapis.grainql.com/v1/api/query/count/your-tenant-id \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_SECRET_KEY" \
-d '{
"event": "purchase_completed",
"filterSet": [
{
"property": "properties.price",
"comparison": "GREATER_THAN",
"value": 100
}
]
}'
Multiple Filters
Count events from specific users on mobile devices:
curl -X POST https://queryapis.grainql.com/v1/api/query/count/your-tenant-id \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_SECRET_KEY" \
-d '{
"filterSet": [
{
"property": "userId",
"comparison": "IN",
"value": ["user_123", "user_456", "user_789"]
},
{
"property": "properties.device",
"comparison": "EQUALS",
"value": "mobile"
}
]
}'
Conversion Rate Calculation
Calculate conversion rates by comparing counts:
# Count signup page views
curl -X POST https://queryapis.grainql.com/v1/api/query/count/your-tenant-id \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_SECRET_KEY" \
-d '{
"event": "page_viewed",
"filterSet": [
{
"property": "properties.page",
"comparison": "EQUALS",
"value": "/signup"
}
]
}'
# Count account creations
curl -X POST https://queryapis.grainql.com/v1/api/query/count/your-tenant-id \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_SECRET_KEY" \
-d '{
"event": "account_created"
}'
Code Examples
JavaScript/TypeScript
async function countEvents ( tenantId : string , apiKey : string , filters : any ) : Promise < number > {
const response = await fetch ( `https://queryapis.grainql.com/v1/api/query/count/ ${ tenantId } ` , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-API-Key' : apiKey
},
body: JSON . stringify ( filters )
});
if ( ! response . ok ) {
throw new Error ( `Count query failed: ${ response . status } ${ response . statusText } ` );
}
const result = await response . json ();
return result . count ;
}
// Usage
const pageViews = await countEvents ( 'your-tenant-id' , 'your-api-key' , {
event: 'page_viewed' ,
after: '2024-01-01' ,
before: '2024-01-31'
});
console . log ( `Total page views in January: ${ pageViews } ` );
Python
import requests
from typing import Dict, Any
def count_events ( tenant_id : str , api_key : str , filters : Dict[ str , Any]) -> int :
"""Count events matching the given filters"""
response = requests.post(
f 'https://queryapis.grainql.com/v1/api/query/count/ { tenant_id } ' ,
headers = {
'Content-Type' : 'application/json' ,
'X-API-Key' : api_key
},
json = filters
)
response.raise_for_status()
result = response.json()
return result[ 'count' ]
# Usage
purchase_count = count_events( 'your-tenant-id' , 'your-api-key' , {
'event' : 'purchase_completed' ,
'filterSet' : [
{
'property' : 'properties.price' ,
'comparison' : 'GREATER_THAN' ,
'value' : 50
}
]
})
print ( f 'High-value purchases: { purchase_count } ' )
Node.js
const fetch = require ( 'node-fetch' );
async function countEvents ( tenantId , apiKey , filters ) {
const response = await fetch ( `https://queryapis.grainql.com/v1/api/query/count/ ${ tenantId } ` , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-API-Key' : apiKey
},
body: JSON . stringify ( filters )
});
if ( ! response . ok ) {
throw new Error ( `Count query failed: ${ response . status } ${ response . statusText } ` );
}
const result = await response . json ();
return result . count ;
}
// Usage
const errorCount = await countEvents ( 'your-tenant-id' , process . env . GRAIN_API_KEY , {
event: 'error_occurred' ,
after: '2024-01-01' ,
before: '2024-01-31'
});
console . log ( `Errors in January: ${ errorCount } ` );
Dashboard Metrics
Use the count endpoint to build dashboard metrics:
async function getDashboardMetrics ( tenantId : string , apiKey : string ) {
const [ totalUsers , totalEvents , errors , purchases ] = await Promise . all ([
// Count unique users
countEvents ( tenantId , apiKey , {
filterSet: [{ property: 'userId' , comparison: 'IS_NOT_NULL' , value: null }]
}),
// Count all events
countEvents ( tenantId , apiKey , {}),
// Count errors
countEvents ( tenantId , apiKey , { event: 'error_occurred' }),
// Count purchases
countEvents ( tenantId , apiKey , { event: 'purchase_completed' })
]);
return {
totalUsers ,
totalEvents ,
errorRate: errors / totalEvents ,
conversionRate: purchases / totalUsers
};
}
Monitor system health with count queries:
import time
from datetime import datetime, timedelta
def monitor_system_health ( tenant_id : str , api_key : str ):
"""Monitor system health using count queries"""
now = datetime.now()
last_hour = now - timedelta( hours = 1 )
# Count events in last hour
total_events = count_events(tenant_id, api_key, {
'after' : last_hour.strftime( '%Y-%m- %d ' ),
'before' : now.strftime( '%Y-%m- %d ' )
})
# Count errors in last hour
error_events = count_events(tenant_id, api_key, {
'event' : 'error_occurred' ,
'after' : last_hour.strftime( '%Y-%m- %d ' ),
'before' : now.strftime( '%Y-%m- %d ' )
})
error_rate = error_events / total_events if total_events > 0 else 0
if error_rate > 0.05 : # 5% threshold
print ( f "🚨 High error rate: { error_rate :.2%} " )
# Send alert
else :
print ( f "✅ System healthy: { error_rate :.2%} error rate" )
# Run every 5 minutes
while True :
monitor_system_health( 'your-tenant-id' , 'your-api-key' )
time.sleep( 300 )
A/B Testing
Use count queries to analyze A/B test results:
async function analyzeABTest ( tenantId : string , apiKey : string , testName : string ) {
const [ variantA , variantB , conversionsA , conversionsB ] = await Promise . all ([
// Count users who saw variant A
countEvents ( tenantId , apiKey , {
event: 'experiment_viewed' ,
filterSet: [
{ property: 'properties.experiment' , comparison: 'EQUALS' , value: testName },
{ property: 'properties.variant' , comparison: 'EQUALS' , value: 'A' }
]
}),
// Count users who saw variant B
countEvents ( tenantId , apiKey , {
event: 'experiment_viewed' ,
filterSet: [
{ property: 'properties.experiment' , comparison: 'EQUALS' , value: testName },
{ property: 'properties.variant' , comparison: 'EQUALS' , value: 'B' }
]
}),
// Count conversions for variant A
countEvents ( tenantId , apiKey , {
event: 'conversion_completed' ,
filterSet: [
{ property: 'properties.experiment' , comparison: 'EQUALS' , value: testName },
{ property: 'properties.variant' , comparison: 'EQUALS' , value: 'A' }
]
}),
// Count conversions for variant B
countEvents ( tenantId , apiKey , {
event: 'conversion_completed' ,
filterSet: [
{ property: 'properties.experiment' , comparison: 'EQUALS' , value: testName },
{ property: 'properties.variant' , comparison: 'EQUALS' , value: 'B' }
]
})
]);
const conversionRateA = conversionsA / variantA ;
const conversionRateB = conversionsB / variantB ;
return {
variantA: { views: variantA , conversions: conversionsA , rate: conversionRateA },
variantB: { views: variantB , conversions: conversionsB , rate: conversionRateB },
winner: conversionRateA > conversionRateB ? 'A' : 'B'
};
}
Error Handling
Handle count query errors gracefully:
async function countEventsWithRetry ( tenantId : string , apiKey : string , filters : any , maxRetries = 3 ) : Promise < number > {
for ( let attempt = 1 ; attempt <= maxRetries ; attempt ++ ) {
try {
const response = await fetch ( `https://queryapis.grainql.com/v1/api/query/count/ ${ tenantId } ` , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-API-Key' : apiKey
},
body: JSON . stringify ( filters )
});
if ( response . status === 429 ) {
const retryAfter = response . headers . get ( 'Retry-After' );
const delay = retryAfter ? parseInt ( retryAfter ) * 1000 : Math . pow ( 2 , attempt ) * 1000 ;
console . log ( `Rate limited. Retrying in ${ delay } ms...` );
await new Promise ( resolve => setTimeout ( resolve , delay ));
continue ;
}
if ( ! response . ok ) {
throw new Error ( `Count query failed: ${ response . status } ${ response . statusText } ` );
}
const result = await response . json ();
return result . count ;
} catch ( error ) {
if ( attempt === maxRetries ) throw error ;
await new Promise ( resolve => setTimeout ( resolve , Math . pow ( 2 , attempt ) * 1000 ));
}
}
throw new Error ( 'Max retries exceeded' );
}
1. Use Date Ranges
Always include date ranges to limit the data scanned:
{
"after" : "2024-01-01" ,
"before" : "2024-01-31"
}
2. Be Specific with Filters
Use specific filters to reduce the dataset:
{
"event" : "purchase_completed" ,
"filterSet" : [
{ "property" : "properties.price" , "comparison" : "GREATER_THAN" , "value" : 100 }
]
}
3. Cache Results
Cache count results for frequently accessed metrics:
const cache = new Map ();
const CACHE_TTL = 5 * 60 * 1000 ; // 5 minutes
async function getCachedCount ( tenantId : string , apiKey : string , filters : any ) : Promise < number > {
const key = JSON . stringify ( filters );
const cached = cache . get ( key );
if ( cached && Date . now () - cached . timestamp < CACHE_TTL ) {
return cached . count ;
}
const count = await countEvents ( tenantId , apiKey , filters );
cache . set ( key , { count , timestamp: Date . now () });
return count ;
}
Custom Plans
Need higher rate limits for your count queries? We offer custom plans with:
Higher rate limits : Custom requests per minute/day limits
Dedicated support : Priority support and SLA guarantees
Volume discounts : Competitive pricing for high-volume usage
Contact us at support@grainql.com to discuss your requirements.
Next Steps
Query Events Learn how to query events with filters
List Events Discover available event types
Filter Reference Complete guide to all filter operators
Custom Dashboard Build a custom analytics dashboard