This guide will help you quickly get up and running with the Facilitron Public API. Follow these steps to set up your integration and start making API calls.
Prerequisites
Before you begin, you'll need:
A Facilitron partnership agreement
API credentials (API User ID and API Key) obtained from Facilitron
Basic knowledge of REST APIs and HTTP requests
A tool or library for making HTTP requests (e.g., cURL, Postman, or a programming language's HTTP client)
Step 1: Obtain API Credentials
Contact [email protected] to request API credentials. You'll receive:
API User ID : Identifies your organization in the Facilitron system
API Key : A secret token used for authentication
These credentials will determine which properties (schools/districts) you have access to.
Step 2: Make Your First API Call
Let's start by retrieving the list of properties you have access to:
curl --location --request GET 'https://api.external.facilitron.com/v1/properties' \
--header 'Authorization: Bearer YOUR_API_KEY'
This should return a list of properties (schools and districts) that your API credentials have access to.
Step 3: Explore the Properties Structure
The properties endpoint returns an array where the district office (parent location) is typically at position 0, followed by individual schools (child locations). Note that the parent / district object includes a properties array containing the IDs of all children / schools in that parent / district.
Example response:
{
"message" : "Properties retrieved successfully – The property details have been successfully retrieved." ,
"message_code" : "A007" ,
"invocation_id" : "302215af-612e-4fc5-92ec-285ed0c19feb" ,
"properties" : [
{
"_id" : "67d3d98234e4730d41310bd2" ,
"u_id" : "139d71e8662595e3ba37" ,
"name" : "Some High School District" ,
"address" : "285 West Grand Ave" ,
"city" : "Sunnyvale" ,
"state" : "CA" ,
"zip" : "94087" ,
"timezone" : "America/Los_Angeles" ,
"properties" : [ "67d3ca78934e4730d41310bd9" , "67d3cb01934e4730d41310be0" ]
},
{
"_id" : "67d3ca78934e4730d41310bd9" ,
"u_id" : "283f71c5237595e3ba42" ,
"name" : "Some High School" ,
"address" : "1 Market St" ,
"city" : "Sunnyvale" ,
"state" : "CA" ,
"zip" : "94087" ,
"timezone" : "America/Los_Angeles" ,
"properties" : []
},
{
"_id" : "67d3cb01934e4730d41310be0" ,
"u_id" : "283f71c5237595e3ba42" ,
"name" : "Some Middle School" ,
"address" : "1 Facilitron Way" ,
"city" : "Sunnyvale" ,
"state" : "CA" ,
"zip" : "94087" ,
"timezone" : "America/Los_Angeles" ,
"properties" : []
}
]
}
Step 4: Get Facilities for a Property
Now that you have property IDs, you can retrieve the facilities for a specific property:
curl --location --request GET 'https://api.external.facilitron.com/v1/property/67d3ca78934e4730d41310bd9/facilities' \
--header 'Authorization: Bearer YOUR_API_KEY'
This returns all active facilities for the specified property:
{
"message" : "Facilities retrieved successfully – The facility details have been successfully retrieved." ,
"message_code" : "A006" ,
"invocation_id" : "578dfa87-8e45-4df6-82da-5e137d9fc072" ,
"facilities" : [
{
"_id" : "67d412fd8bc3c50a13fcbf3c" ,
"u_id" : "69eb17597ecc1350e86f" ,
"name" : "Main Gymnasium" ,
"capacity" : 500 ,
"type" : "Gymnasium" ,
"search_term" : "Gymnasium" ,
"property_id" : "67d3ca78934e4730d41310bd9"
}
]
}
Step 5: Query Events for a Facility
Now that you have facility IDs, you can retrieve events for a specific facility:
curl --location --request GET 'https://api.external.facilitron.com/v1/facility/67d412fd8bc3c50a13fcbf3c/events?start_date=2024-06-01&end_date=2024-06-30&event_types=reservation' \
--header 'Authorization: Bearer YOUR_API_KEY'
This will return all events of type "reservation" for the specified facility between June 1-30, 2024.
Step 6: Handle Date Ranges Properly
Remember these important date range rules:
Maximum date range in a single query: 31 days
Past dates are not available
Dates should be in the owner's timezone (specified in property details)
Use ISO 8601 format: YYYY-MM-DD
Invalid date ranges will return a SY001 (Validation failed) error rather than a specialized event error.
Step 7: Implement Error Handling
Always check for error codes in responses. Here's a simple error handling pattern in JavaScript:
async function getFacilities ( propertyId ) {
try {
const response = await fetch ( `https://api.external.facilitron.com/v1/property/${ propertyId }/facilities` , {
headers: {
'Authorization' : 'Bearer YOUR_API_KEY' ,
}
});
// Handle authentication errors (401) which have a different format
if (response.status === 401 ) {
const errorData = await response. json ();
console. error ( `Authentication Error: ${ errorData . message }` );
console. error ( `Request ID: ${ errorData . invocation_id }` );
return null ;
}
const data = await response. json ();
// Handle standard API errors
if (data.message_code && ! data.message_code. startsWith ( 'A' )) {
console. error ( `API Error: ${ data . message_code } - ${ data . message }` );
return null ;
}
return data.facilities;
} catch (error) {
console. error ( 'Network error:' , error);
return null ;
}
}
Step 8: Implement Rate Limit Handling
The API has a default quota of 1,000 requests per 24-hour period. Implement exponential backoff for handling rate limits:
async function fetchWithBackoff ( url , options , maxRetries = 3 ) {
let retries = 0 ;
while (retries < maxRetries) {
try {
const response = await fetch (url, options);
if (response.status === 429 ) {
// Rate limit exceeded - Note that 429 responses have a simpler structure
// and don't include message_code or invocation_id
const backoffTime = Math. pow ( 2 , retries) * 1000 ;
console. log ( `Rate limit exceeded. Retrying after ${ backoffTime }ms...` );
await new Promise ( resolve => setTimeout (resolve, backoffTime));
retries ++ ;
continue ;
}
return response;
} catch (error) {
const backoffTime = Math. pow ( 2 , retries) * 1000 ;
await new Promise ( resolve => setTimeout (resolve, backoffTime));
retries ++ ;
}
}
throw new Error ( 'Maximum retries exceeded' );
}
Step 9: Cache Data When Possible
To minimize API calls, cache data that doesn't change frequently:
Property and facility data (refresh weekly)
Event data (refresh daily or on-demand)
Example caching strategy in pseudocode:
// Simple in-memory cache
const cache = {
properties: { data: null , timestamp: 0 },
facilities: {}, // Keyed by propertyId
events: {} // Keyed by facilityId + date range
};
const CACHE_TTL = {
PROPERTIES: 365 * 24 * 60 * 60 * 1000 , // 365 days
FACILITIES: 30 * 24 * 60 * 60 * 1000 , // 30 days
EVENTS: 60 * 60 * 1000 // 1 hour
};
async function getPropertiesWithCache () {
const now = Date. now ();
// Check if cache is valid
if (cache.properties.data && (now - cache.properties.timestamp) < CACHE_TTL . PROPERTIES ) {
return cache.properties.data;
}
// Fetch fresh data
const properties = await fetchProperties ();
// Update cache
cache.properties = {
data: properties,
timestamp: now
};
return properties;
}
Step 10: Create a Streamlined Workflow
Typical integration workflow:
Fetch and cache properties on startup or as needed
Fetch and cache facilities for each property as needed
Fetch events for specific facilities as needed
Handle updates on a regular schedule or on-demand
Code Examples
Python Example
import requests
import time
from datetime import datetime, timedelta
API_BASE_URL = 'https://api.external.facilitron.com/v1'
API_KEY = 'YOUR_API_KEY'
def get_properties ():
headers = {
'Authorization' : 'Bearer' + API_KEY
}
response = requests.get( f " {API_BASE_URL} /properties" , headers = headers)
if response.status_code == 200 :
data = response.json()
if data.get( 'message_code' , '' ).startswith( 'A' ):
return data.get( 'properties' , [])
else :
print ( f "API Error: { data.get( 'message_code' ) } - { data.get( 'message' ) } " )
elif response.status_code == 429 :
print ( "Rate limit exceeded. Please try again later." )
else :
print ( f "Error: { response.status_code } " )
return []
def get_facilities (property_id):
headers = {
'Authorization' : 'Bearer' + API_KEY
}
response = requests.get( f " {API_BASE_URL} /property/ { property_id } /facilities" , headers = headers)
if response.status_code == 200 :
data = response.json()
if data.get( 'message_code' , '' ).startswith( 'A' ):
return data.get( 'facilities' , [])
else :
print ( f "API Error: { data.get( 'message_code' ) } - { data.get( 'message' ) } " )
return []
def get_events (facility_id, start_date = None , end_date = None ):
if not start_date:
start_date = datetime.now().strftime( '%Y-%m- %d ' )
if not end_date:
# Default to 30 days from start_date
end_date = (datetime.strptime(start_date, '%Y-%m- %d ' ) + timedelta( days = 30 )).strftime( '%Y-%m- %d ' )
headers = {
'Authorization' : 'Bearer' + API_KEY
}
params = {
'start_date' : start_date,
'end_date' : end_date,
'event_types' : 'reservation'
}
response = requests.get( f " {API_BASE_URL} /facility/ { facility_id } /events" , headers = headers, params = params)
if response.status_code == 200 :
data = response.json()
if data.get( 'message_code' , '' ).startswith( 'A' ):
return data.get( 'events' , [])
else :
print ( f "API Error: { data.get( 'message_code' ) } - { data.get( 'message' ) } " )
return []
# Example usage
properties = get_properties()
for prop in properties:
print ( f "Property: { prop.get( 'name' ) } " )
facilities = get_facilities(prop.get( '_id' ))
for facility in facilities:
print ( f " Facility: { facility.get( 'name' ) } " )
events = get_events(facility.get( '_id' ))
for event in events:
print ( f " Event: { event.get( 'name' ) } on { event.get( 'submit_date' ) } " )
JavaScript (Node.js) Example
const axios = require ( 'axios' );
const API_BASE_URL = 'https://api.external.facilitron.com/v1' ;
const API_KEY = 'YOUR_API_KEY' ;
// Create axios instance with default headers
const api = axios. create ({
baseURL: API_BASE_URL ,
headers: {
'Authorization' : 'Bearer' + API_KEY
}
});
// Add response interceptor for error handling
api.interceptors.response. use (
response => {
const data = response.data;
// Check for API-level errors
if (data.message_code && ! data.message_code. startsWith ( 'A' )) {
console. error ( `API Error: ${ data . message_code } - ${ data . message }` );
return Promise . reject ( new Error ( `API Error: ${ data . message }` ));
}
return response;
},
error => {
if (error.response) {
// Request was made and server responded with error status
if (error.response.status === 401 ) {
// Special handling for authentication errors
const errorData = error.response.data;
console. error ( `Authentication Error: ${ errorData . detail }` );
console. error ( `Request ID: ${ errorData . trace . requestId }` );
} else if (error.response.status === 429 ) {
// Rate limit error
console. error ( 'Rate limit exceeded. Please try again later.' );
} else {
console. error ( `HTTP Error: ${ error . response . status } - ${ error . response . statusText }` );
}
} else if (error.request) {
// Request was made but no response received
console. error ( 'No response received from server' );
} else {
// Something else happened
console. error ( 'Error:' , error.message);
}
return Promise . reject (error);
}
);
async function getProperties () {
try {
const response = await api. get ( '/properties' );
return response.data.properties || [];
} catch (error) {
console. error ( 'Failed to get properties:' , error.message);
return [];
}
}
async function getFacilities ( propertyId ) {
try {
const response = await api. get ( `/property/${ propertyId }/facilities` );
return response.data.facilities || [];
} catch (error) {
console. error ( `Failed to get facilities for property ${ propertyId }:` , error.message);
return [];
}
}
async function getEvents ( facilityId , startDate , endDate ) {
try {
// Default dates if not provided
if ( ! startDate) {
startDate = new Date (). toISOString (). split ( 'T' )[ 0 ]; // Today in YYYY-MM-DD
}
if ( ! endDate) {
// Default to 30 days from start
const end = new Date (startDate);
end. setDate (end. getDate () + 30 );
endDate = end. toISOString (). split ( 'T' )[ 0 ];
}
const response = await api. get ( `/facility/${ facilityId }/events` , {
params: {
start_date: startDate,
end_date: endDate,
event_types: 'reservation'
}
});
return response.data.events || [];
} catch (error) {
console. error ( `Failed to get events for facility ${ facilityId }:` , error.message);
return [];
}
}
// Example usage
async function main () {
try {
const properties = await getProperties ();
console. log ( `Found ${ properties . length } properties` );
for ( const property of properties) {
console. log ( `Property: ${ property . name } (${ property . _id })` );
const facilities = await getFacilities (property._id);
console. log ( `Found ${ facilities . length } facilities` );
for ( const facility of facilities) {
console. log ( `Facility: ${ facility . name } (${ facility . _id })` );
const events = await getEvents (facility._id);
console. log ( `Found ${ events . length } events` );
for ( const event of events) {
console. log ( `Event: ${ event . name } on ${ event . submit_date }` );
}
}
}
} catch (error) {
console. error ( 'Main process error:' , error.message);
}
}
main ();
Next Steps
Now that you've completed this getting started guide, you can:
Explore the API Overview for more detailed information
Refer to the OpenAPI specification API Reference for detailed endpoint references
Review the Error Handling Guide for best practices
If you need help, visit the Support Doc
If you have any questions or need further assistance, contact [email protected] .
Last modified on November 14, 2025