Introduction
Welcome to the Scheduling Center API! You can use our API to access a number of Scheduling Center functions, from invoicing to scheduling and more.
Current Version: v1.6.3 Short Stack
Authentication
While normally the Scheduling Center uses cookies to store authentication (and optionally you can continue to do so at your discretion), most features can also be accessed statelessly with Basic Authentication.
All of the API endpoints listed in this document can be accessed using this authentication protocol as long as you
prefix the URL with /api/
.
For example:
https://crm.oxifresh.com/api/reviews.json
Without /api/
, you'll be limited to cookie-based authentication with a standard usernaame
/password
combo you first
POST to:
https://crm.oxifresh.com/employees/login
API Keys
To authenticate, use this code:
<?php
/**
* To use Basic Authentication in PHP, include CURLOPT_USERPWD in all curl requests. For example:
*/
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/api/reviews.json');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if ( ! in_array($info['http_code'], array('200', '201', '204'))) {
// Success if a 200 code
}
?>
/**
* Our examples will be using the request library with the following defaults:
* See https://www.npmjs.com/package/request
*/
var request = require('request')
// We'll also have set up these defaults:
baseRequest = request.defaults({
json: true, // Examples will be sending object data in JSON format
headers: {
'X-Requested-With': 'XMLHttpRequest' // This will signal you want AJAX-y responses instead of HTML
},
auth:{ // To use Basic authentication, with the /api endpoints, list your username and API Key:
user: 'api_username',
password: 'api_key'
}
})
// Example request:
var data = {franchsie_id: 2}
baseRequest({
url: "https://192.168.50.4/api/categories.json?",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
console.log(body)
}
}
)
/**
* To use Basic Authentication with jQuery, include beforeSend in all requests. For example:
*/
jQuery.ajax({
url: '/reviews.json',
success: function (obj) {
// Resutls are in obj.review ..
},
beforeSend: function (xhr) {
xhr.setRequestHeader ("Authorization", "Basic " + btoa(api_username + ":" + api_password));
},
});
Since Basic Authentication requests are set in plain-text, we only allow the use of API Keys (in place of user's normal passwords). For the same reason, we only support HTTPS requests.
You will need to be given an API username & API Key by a system admin to begin.
Basic Authentication PHP
Per PHP curl documentation this is specified as a colon (:
) separated string:
curl_setopt($curl_handle, CURLOPT_USERPWD, 'api_username:api_password');
NodeJS
A number of libraries are available to assist with sending HTTP requests. In this documentation our examples will be using
the request library. The auth
config option will send Basic authentication
headers with the request:
auth:{ user: 'api_username', password: 'api_key' }
In the Browser
And in jQuery can be listed in the beforeSend function, setting the Authorization
header using btoa
:
xhr.setRequestHeader ("Authorization", "Basic " + btoa(api_username + ":" + api_password));
General Usage
Routing
We've updated our application to follow more common industry standard URL routes, which rely on HTTP Methods to determine what action to take.
In general all controllers will accept at least GET, POST, PATCH, PUT and DELETE HTTP methods.
For example, the reviews
endpoint will respond to the following methods:
HTTP Method | Example | Description |
---|---|---|
GET | /reviews |
Returns a list of reviews |
GET | /reviews/:review_id |
Returns details on a single review by ID |
POST | /reviews |
Creates a new Review (with the review data passed in the request body) |
PUT or PATCH | /reviews/:review_id |
Updates a review (all fields passed are updated, any left out are left as-is) |
DELETE | /reviews/:review_id |
Deletes a single review by ID |
The API may support custom actions, if so they'll follow similar HTTP-method conventions, ex:
HTTP Method | Example | Description |
---|---|---|
POST | /servicesSettings/copyCorporate/:corporate_service_id |
Duplicates a Corporate service by ID down to a specific Franchisee |
DELETE | /service/fullDelete/:service_id |
Hard-deletes a Corporate service by ID |
Old Route Style (Legacy Support)
For now, we'll continue to support the legacy routes, which required you state the action in the URL explicitly.
Here they are for reference
- index
- This is the default action when none is specified
- Ex. `
https://crm.oxifresh.com/api/reviews/index
-or -https://crm.oxifresh.com/api/reviews
GET
-request only
- view
- Must specify the ID for the action in the URL
- Ex. -
https://crm.oxifresh.com/api/reviews/view/1
GET
-request only
- add
- Must specify data for the new entity in POST
https://crm.oxifresh.com/api/reviews/add
POST
-requests only
- edit
- Must specify the ID for the entity in the URL, and the data for the new entity in POST
- Ex/
https://crm.oxifresh.com/api/reviews/edit/1
POST, PUT, or PATCH
-requests only
- delete
- Must specify the ID of the entity in the URL
https://crm.oxifresh.com/api/reviews/delete/1
DELETE
-request only
Certain controllers may implement additional functions as specified in the docs.
Franchise/Corporate Access
There are two general types of users in our application: 1. corporate users, which have general access to all Franchise accounts, and 2. franchisees users, which only have access to records for a single Franchise
Most likely your account was issued as a corporate
user, which has implicit access to all our Franchise accounts. By
default, list views will show records for all franchise accounts. To limit records to a single franchise, specify a
franchise_id
in the request.
When using Basic Authentication, since you do not have a session between requests, you'll need to always specify
a Franchise ID in the index
request to limit results to just that account.
However when using cookie-based authentication methods, the Franchise ID is stored in your session and remembered between requests.
To reset it back to the 'all franchise' lists, pass franchsie_id=0
in your request.
Output Formats
For simplicity, all requests prefixed with /api/
will return JSON-formatted output.
However you can request several custom formats, either by specifying the type in the Accept:
header:
baseRequest({
url: "https://crm.oxifresh.com/api/invoices",
method: "GET",
headers: {
'Accept': 'application/xml'
}
}
)
... or by appending an extension to the end of the URL, eg:
https://crm.oxifresh.com/api/reviews/1.xml
- Will return XML
https://crm.oxifresh.com/api/reviews/1.json
- Will return JSON
The API provides output in three formats:
- JSON
- Set the Request Header
Accetps: application/json
, or append the request with.json
- Set the Request Header
- XML
- Set the Request Header
Accetps: application/xml
, or append the request with.xml
- Set the Request Header
- CSV (note this has limited support)
- Set the Request Header
Accetps: application/csv
, or append the request with.csv
- Set the Request Header
- HTML
* Omit the
/api/
prefix. Note that you'll have to use Cookie-based Authentication to get the HTML form versions.
Universal Parameters
For all listing requests (eg. GET /reviews
) , the following parameters are always accepted. For brevity, we won't list
them in the rest of the documentation:
Parameter | Default | Description |
---|---|---|
franchise_id | null | Restricts the results to the Franchise account ID specified. To pull records for all franchises, use ID 0. |
page | 1 | Results are paginated, and the default page is 1 (the first set of data). Increment to view the next "page" of entities |
limit | 10 | The default limit of the paginated results is 10 |
Global Results
All listing requests (eg. GET /reviews
) will return the following parameters at the end of the results, as results are paginated. For
brevity, we won't list them in the rest of the documentation:
Parameter | Description |
---|---|
total | The total number of entities matching the input parameters |
page | The current page number of the results |
limit | How many of the total results were returned for this page |
Rate Limits
You will be limited to 3000 requests per hour.
At this time we also ask that you run extraordinarily heavy tasks before 6 AM MST and after 8 PM MST and to ensure an unusual increase in load does not adversely affect our day to day operations.
If you require more frequent updates throughout the day, you may want to check out our Push API, which can forward changes to your application in near-real-time.
Filtering Fields
To filter the fields and associations for Invoices down to just totals and names, for example:
<?php
$curl = curl_init();
// Prep data for the GET request
$data = [
'franchise_id' => 2,
'fields' => json_encode([
"invoices" => ["id", "date_completed", "amount_due"],
"contacts" => ["id", "first_name", "last_name"]])
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/invoices.json?'.$fieldsString);
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {
franchsie_id: 2,
fields: JSON.stringify({
invoice: ["id", "date_completed", "amount_due"],
contacts: ["id", "first_name", "last_name"]
})
}
baseRequest({
url: "https://crm.oxifresh.com/api/invoices.json",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return only the 3 requested fields on Invoices, plus the first and last name for it’s Contact (see sidebar).
{
"invoices": [
{
"id": 910822,
"date_completed": "2017-11-21T15:30:00+00:00",
"amount_due": 125,
"contact_id": 930946,
"contact": {
"id": 930946,
"first_name": "John ",
"last_name": "Smith"
}
}
],
"total": 493,
"page": 1,
"limit": 1,
"fields": {
"Invoices": [
"Invoices.id",
"Invoices.date_completed",
"Invoices.amount_due",
"contact_id"
],
"Contacts": [
"id",
"first_name",
"last_name"
]
},
"matching": {
"InvoicesCampaigns.Campaigns.code": "CLEAN18"
}
}
By default, API endpoints will include all of the fields listed on the entity, as well as a list of commonly useful associated entities and all of their fields. While this can be useful data, it can also be including a lot of information you don't necessarily need for your specific integration.
The Invoices index endpoint, for example, will by default return all the fields for the invoice, plus the associated Invoice Campaigns and their Campaigns fields, Contact information, and Address information. It also contains a couple of “virtual” fields, which are derived values not stored on the entity directly but computed on the fly. For Invoices, the Zone field and the boolean “is_completed” fields are “virtual” fields computed on request.
While this data could be useful, it also can slow down response times significantly at scale. The underlying query is rather large (requiring at least 4 joins and several sub-queries for loading associations & virtual field values), and computing virtual field values is particularly slow at scale.
Examples
If you need to request large sets of data, it can dramatically increase response times if you specify only the list of fields you actually need. This can help eliminate superfluous associations and shrink the size of the response payload significantly. To slim down the list of fields, simply include a fields parameter in your request, containing a JSON-encoded list of the entity fields & associations fields you need.
Note that it now also includes the list of “fields” that matched in your request. If a requested field doesn’t show in this list, double check that it’s valid and also that it’s not a “virtual” field (these are not supported at this time). Also ensure that when specifying association’s fields that you use the base entity table name, which is generally plural regardless of the type of association. In the above example, Invoices have only a single potential Contact, so that’s returned as “contact” in the API data. However the base table is plural, so in the fields list you must specify it as “contacts” (plural).
Also note that the “matching” node is now added when filtering by campaigns code, to help reduce confusion as the dataset may not actually include the invoice campaign data if not specified in the “fields” parameter, even though it was used to filter the results.
Limitations
Note also that at this time it can only support filtering associations one level deep, so it's not possible to select fields for an Invoice's -> Invoice Campaign's -> Campaign.
Wild Cards
You may use wildcards to select all fields for a specific association, for example to select all the Contact's field use an asterisk:
https://crm.oxifresh.com/invoices.json?campaign_code=CLEAN18&limit=1&page=1&fields={"invoices":["id","date_completed","amount_due"],"contacts":["*"]}
API Endpoints:
Below are the details for each public endpoint, listed alphabetically.
Note that a fair number of endpoints are still undocumented but otherwise fully functional. Check out the To-Be-Documented section at the ned for more information.
Appointments
Currently the Appointments API has a limited set of functions - you can currently pull lists of appointments, or get detailed extended data about single appointments. Adding/editing appointments is possible, but must be done in a non-standard form-submission method that exactly mimics the current Calendar front-end in the real app (it is not recommended for external use at this time).
If you need to book appointments, we can review options for you as there are several possible approaches. A simplified API for booking new appointments (and managing jobs in general) is planned for a future release TBD.
List All Appointments
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/appointments?franchise_id=501');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
?>
baseRequest({
url: "https://crm.oxifresh.com/appointments",
method: "GET",
accepts: "application/json",
body: {
'franchise_id' : 501,
}
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"appointments": [
{
"id": 2,
"type_id": 1,
"franchise_id": 501,
"contact_id": 123456,
"invoice_id": 55421,
"created_by_id": 1,
"modified_by_id": 1,
"employee_id": 3020,
"start_datetime": "2018-08-27T11:15:00+00:00",
"end_datetime": "2018-08-27T12:35:00+00:00",
"vehicle_id": null,
"notes": "Gate code 456",
"date_created": "2018-08-27T17:03:34+00:00",
"last_modified": "2018-08-27T17:03:34+00:00",
"cancelled": false,
"cancellation_reason": null,
"cancellation_message": null,
"cancelled_by_id": null,
"date_cancelled": null,
"parked": false,
"parked_by_employee_id": null,
"parent_appointment_id": null,
"address_id": 577005,
"appointments_booking_method_id": 1,
"address": {
"id": 577005,
"entity_type": 4,
"entity_id": 566031,
"template_id": 1,
"type_id": 1,
"template_group_id": null,
"address1": "123 Fake St",
"address2": "Building 2",
"unit_type": 1,
"unit_number": "23",
"county": null,
"city": "Lakewood",
"state": "CO",
"zipcode": "80228",
"country_id": 1,
"active": 1,
"geocode": "39.716442,-105.134725",
"latitude": null,
"longitude": null,
"unit_type_name": null,
"full_street": "123 Fake St Building 2",
"country_code": "US",
"type_name": "Service"
},
"appointments_type": {
"id": 1,
"name": "Job Scheduled",
"short_name": null,
"lookupKey": null,
"duration": 3600,
"isJobType": true,
"active": true
}
}
]
}
Lists all appointment information, including any address, and the type of appointment.
Filtering & Date Limiting
Similar to the Invoices endpoint, whenever an appointment is updated, it's last_modified
field is updated. You can limit results to
those updated after a specific date with the last_modified_after
parameter.
You can also reduce the size of the response by using the standard filtering methods outlined in the general Filtering Fields docs.
Bulk Listings
The default page size is just 10 records, but you can increase that limit significantly with limit
.
If the limit is too high, requests can timeout or memory overflow and the API will return a 500-coded response.
HTTP Request
GET http://crm.oxifresh.com/appointments
Response
A list of Appointment entities with the following fields:
Parameter | Description |
---|---|
type_id | A reference to the AppointmentsType (1 for a Job, 2 Estimate, 5 Re-do, 6 Other, 7 Marketing, 9 Pull-job, 9 Online Reservation) |
franchise_id | The Franchise ID the appointment is associated with |
contact_id | The ID of the customer the appointment is for |
invoice_id | A linked Invoice, if available |
created_by_id | The Employee ID of who created the appointment |
modified_by_id | The Employee ID of the last person to modify the appointment |
employee_id | The Employee ID of the person assigned to go to the Appointment |
start_datetime | The Franchisee-local datetime the appointment starts at |
end_datetime | The Franchisee-local datetime the appointment ends at |
vehicle_id | DEPRECATED - The Vehicle ID assigned to the job |
notes | Any comments added manually durring booking |
date_created | UTC datetime the appointment was created |
last_modified | UTC datetime the appointment was last modified |
cancelled | Boolean true/false if the appointment is cancelled |
cancellation_reason | A CancellationReason ID (Deprecated - all of them will be 13 as of now) |
cancellation_message | Comments manually entered about why the job was cancelled |
cancelled_by_id | The Employee ID of the person who cancelled it |
date_cancelled | The UTC datetime the job was cancelled |
parked | Boolean true/false if the job has been "parked" in preparation for rescheduling it to another day/time |
parked_by_employee_id | The Employee ID of who parked it last |
parent_appointment_id | If associated to a parent Appointment (Pull jobs usually are), that ID |
address_id | The Address ID of the appointment location |
appointments_booking_method_id | The Appointment Booking Method ID that indicates how it was booked (1 for Call Center, 2 Online Scheduler, 3 Chat Bot) |
Campaigns
Marketing activities are grouped into Campaigns, which are single-use, optionally date-ranged records of a marketing spend or activity.
They all specify a Code, which might be printed on a physical coupon pack, or posted online, to use for easy lookup on the phone or when booking online.
They also can optionally list a fixed cost - used for ROI (return on investment) calculations in reports.
Campaigns historically would list a set of Coupons under each record, however they now can instead specify a pre-built, re-usable Coupon Pack instead, to share coupons between multiple Campaigns.
Get a list of campaigns
<?php
$curl = curl_init();
$data = [
'franchise_id' => 2
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/campaigns?'.$fieldsString);
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
?>
var data = {franchsie_id: 2}
baseRequest({
url: "https://crm.oxifresh.com/api/campaigns",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"campaigns": [
{
"id": 1,
"franchise_id": 2,
"coupons_pack_id": null,
"campaigns_category_id": 10,
"campaigns_subcategory_id": 1,
"start_date": "2018-01-01T00:00:00+00:00",
"end_date": "2018-03-01T00:00:00+00:00",
"name": "January Valpak 2018",
"code": "VP0110",
"description": "Mailed to 320,000 households on the second week of the month",
"cost": 5785,
"last_modified": "2016-03-09T12:29:51+00:00",
"date_created": null,
"hidden": false,
"active": false,
"coupons": [
{
"id": 1,
"franchise_id": 2,
"coupons_pack_id": null,
"campaign_id": 1,
"name": "Whole House Carpet Cleaning",
"description": "6 Room Special $168",
"start_date": "2018-01-25T00:00:00+00:00",
"expiration_date": "2018-03-01T00:00:00+00:00",
"created_by_employee_id": 0,
"last_modified_by_employee_id": 0,
"date_created": "2018-01-25T18:24:22+00:00",
"last_modified": "2014-04-09T15:39:38+00:00",
"cannot_be_combined": false,
"allow_total_under_minimum": false,
"list_order": 0,
"active": false,
"expired": true
},
{
"id": 2,
"franchise_id": 2,
"coupons_pack_id": null,
"campaign_id": 1,
"name": "2 Room Special",
"description": "2 Rooms Cleaned $68",
"start_date": "2018-01-25T00:00:00+00:00",
"expiration_date": "2008-11-30T00:00:00+00:00",
"created_by_employee_id": 0,
"last_modified_by_employee_id": 0,
"date_created": "2018-01-25T18:41:25+00:00",
"last_modified": "2014-04-09T15:39:38+00:00",
"cannot_be_combined": false,
"allow_total_under_minimum": false,
"list_order": 0,
"active": false,
"expired": true
}
],
"coupons_pack": null,
"campaigns_subcategory": {
"id": 1,
"franchise_id": 2,
"campaigns_category_id": 10,
"name": "ValPak",
"unknown_default": false,
"active": true,
"hidden": 0
},
"campaigns_category": {
"id": 10,
"icon": "envelope-o",
"name": "Shared Mail",
"abbreviation": null,
"campaigns_categories_cost_type_id": 1,
"display_order": 3,
"reportable": true,
"active": true
}
},
{
"id": 39710,
"franchise_id": 2,
"coupons_pack_id": 2,
"campaigns_category_id": 7,
"campaigns_subcategory_id": 16413,
"start_date": "2018-01-22T00:00:00+00:00",
"end_date": null,
"name": "Online PPC",
"code": "PPC0114",
"description": "",
"cost": 4210,
"last_modified": "2018-01-22T16:55:55+00:00",
"date_created": "2018-01-22T16:12:03+00:00",
"hidden": false,
"active": false,
"coupons": [],
"coupons_pack": {
"id": 2,
"name": "Default Coupon Pack",
"franchise_id": 2,
"date_created": "2017-10-10T16:35:50+00:00",
"last_modified": "2017-11-14T10:53:08+00:00",
"created_by_employee_id": 1,
"last_modified_by_employee_id": 1,
"is_default": true,
"active": true,
"coupons": [
{
"id": 83353,
"franchise_id": 2,
"coupons_pack_id": 2,
"campaign_id": 25257,
"name": "2 Room Special ",
"description": "2 Rooms for $68",
"start_date": "2015-08-05T00:00:00+00:00",
"expiration_date": null,
"created_by_employee_id": 0,
"last_modified_by_employee_id": 598,
"date_created": "2015-08-05T14:55:00+00:00",
"last_modified": "2017-12-31T07:26:01+00:00",
"cannot_be_combined": false,
"allow_total_under_minimum": false,
"list_order": 0,
"active": false,
"expired": false
},
{
"id": 96738,
"franchise_id": 2,
"coupons_pack_id": 2,
"campaign_id": null,
"name": "Three Rooms Special ",
"description": "3 Rooms Cleaned $70",
"start_date": "2016-08-02T00:00:00+00:00",
"expiration_date": null,
"created_by_employee_id": 0,
"last_modified_by_employee_id": 1,
"date_created": "2016-08-02T15:44:53+00:00",
"last_modified": "2017-11-14T03:53:29+00:00",
"cannot_be_combined": true,
"allow_total_under_minimum": false,
"list_order": 0,
"active": true,
"expired": false
}
]
},
"campaigns_subcategory": {
"id": 16413,
"franchise_id": 2,
"campaigns_category_id": 7,
"name": "Test Subcategory 2 - The Retesting",
"unknown_default": false,
"active": false,
"hidden": 0
},
"campaigns_category": {
"id": 7,
"icon": "wifi",
"name": "Radio",
"abbreviation": null,
"campaigns_categories_cost_type_id": 1,
"display_order": 10,
"reportable": true,
"active": true
}
}
]
}
This endpoint gets a paginated list of Campaigns, along with their parent Category & Subcategory, and Coupons.
Note that older Campaigns will list a set of unique Coupons, while newer Campaigns will be linked to a Coupon Pack by specifying
a coupons_pack_id
. The details for the shared Coupon Pack & it's Coupons are included in the results instead.
HTTP Request
GET http://crm.oxifresh.com/campaigns
Query Parameters
Parameter | Default | Description |
---|---|---|
code | null | A specific campaign code to match against |
campaigns_subcategory_id | null | Restricts list to those that match the specified Campaigns Subcategory ID |
Response
Parameter | Description |
---|---|
campaigns | A paginated list of campaigns, includes a count of the number of active Coupons listed for this Campaign. |
total | The total number of campaigns matching the input criteria |
Get a Specific Campaign
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/campaigns/1');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
var data = {franchsie_id: 2}
baseRequest({
url: "https://crm.oxifresh.com/api/campaigns/1",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"campaign": {
"id": 1,
"franchise_id": 2,
"campaigns_category_id": 10,
"campaigns_subcategory_id": 1,
"start_date": "2017-01-01T00:00:00+00:00",
"end_date": "2017-03-01T00:00:00+00:00",
"name": "January Valpak 2018",
"code": "VP0117",
"description": "Mailed to 320,000 households on the second week of the month",
"cost": 5785,
"last_modified": "2016-03-09T19:29:51+00:00",
"date_created": "2016-03-09T19:29:51+00:00",
"hidden": false,
"active": true,
"campaigns_subcategory": {
"id": 1,
"franchise_id": 2,
"campaigns_category_id": 10,
"name": "ValPak",
"active": true,
"hidden": 0
},
"campaigns_category": {
"id": 10,
"icon": "envelope-o",
"name": "Shared Mail",
"campaigns_categories_cost_type_id": 1,
"display_order": 3,
"active": true
}
}
}
This endpoint retrieves a specific campaign by ID.
HTTP Request
GET http://crm.oxifresh.com/campaigns/<ID>
Query Parameters
Parameter | Description |
---|---|
ID | The ID of the campaign to retrieve |
Response
Parameter | Description |
---|---|
campaign | The campaign data |
Add A Campaign
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/campaigns');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$data = [
'name' => 9,
'franchise_id' => 2,
'start_date' => '2017-05-01',
'campaigns_category_id' => 10,
'campaigns_subcategory_id' => 1,
'code' => 'TESTCODE',
'description' => 'Test campaign'
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $fieldsString );
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {
'name' : 9,
'franchise_id' : 2,
'start_date' : '2017-05-01',
'campaigns_category_id' : 10,
'campaigns_subcategory_id' : 1,
'code' : 'TESTCODE',
'description' : 'Test campaign'
}
baseRequest({
url: "https://crm.oxifresh.com/api/campaigns",
method: "POST",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"campaign": {
"id": 1,
"franchise_id": 2,
"campaigns_category_id": 10,
"campaigns_subcategory_id": 1,
"start_date": "2017-05-01T00:00:00+00:00",
"end_date": "",
"name": "Test Campaign",
"code": "TESTCODE",
"description": "Test campaign",
"cost": 0,
"last_modified": "2016-03-09T19:29:51+00:00",
"date_created": "2016-03-09T19:29:51+00:00",
"hidden": false,
"active": true,
"campaigns_subcategory": {
"id": 1,
"franchise_id": 2,
"campaigns_category_id": 10,
"name": "ValPak",
"active": true,
"hidden": 0
},
"campaigns_category": {
"id": 10,
"icon": "envelope-o",
"name": "Shared Mail",
"campaigns_categories_cost_type_id": 1,
"display_order": 3,
"active": true
}
}
}
This endpoint adds a new campaign to the system.
HTTP Request
POST http://crm.oxifresh.com/campaigns/<ID>
Data Parameters
Parameter | Optional | Default | Description |
---|---|---|---|
franchise_id | No | null | The franchise ID this belongs in |
name | No | null | The name of the campaign |
code | No | null | The short lookup code for the campaign, presented to the customer (recommended format XXMMYY where XX is 2-letter shortenting of the name, MM and YY are the month & year) |
campaigns_category_id | No | null | The Campaigns Category ID to list this under. |
campaigns_subcategory_id | No | null | The Campaigns Subcategory ID to list this under (must be listed in the Campaigns Category and this Franchise) |
start_date | No | null | The effective start date of the campaign |
end_date | Yes | null | The effective end date of the campaign |
description | Yes | null | An optional description |
cost | Yes | null | How much the marketing activty costs, for ROI calculations in Reports |
Response
Parameter | Description |
---|---|
campaign | The new campaign data |
Delete a Campaign
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/campaigns/delete/4');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
baseRequest({
url: "https://crm.oxifresh.com/api/campaigns/delete/4",
method: "DELETE"
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"campaign": {
"id": 1,
"franchise_id": 2,
"campaigns_category_id": 10,
"campaigns_subcategory_id": 1,
"start_date": "2017-05-01T00:00:00+00:00",
"end_date": "",
"name": "Test Campaign",
"code": "TESTCODE",
"description": "Test campaign",
"cost": 0,
"last_modified": "2016-03-09T19:29:51+00:00",
"date_created": "2016-03-09T19:29:51+00:00",
"hidden": false,
"active": false
}
}
This endpoint deletes a specific campaign by ID. Note that it's a "soft delete" - marking the active
flag false.
Deactivating the record will only prevent it from being searched for and added on to new jobs. To completely remove
it from listings, use the hide
endpoint.
HTTP Request
DELETE http://crm.oxifresh.com/campaigns/delete/<ID>
Query Parameters
Parameter | Description |
---|---|
ID | The ID of the campaign to delete |
Response
Parameter | Description |
---|---|
campaign | The "deleted" campaign data |
Hide a Campaign
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/campaigns/hide/4');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
baseRequest({
url: "https://crm.oxifresh.com/api/campaigns/hide/4",
method: "DELETE"
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"campaign": {
"id": 1,
"franchise_id": 2,
"campaigns_category_id": 10,
"campaigns_subcategory_id": 1,
"start_date": "2017-05-01T00:00:00+00:00",
"end_date": "",
"name": "Test Campaign",
"code": "TESTCODE",
"description": "Test campaign",
"cost": 0,
"last_modified": "2016-03-09T19:29:51+00:00",
"date_created": "2016-03-09T19:29:51+00:00",
"hidden": true,
"active": false
}
}
As campaigns are meant to be occasionally activated or deactivated, it's active
flag is used just to prevent it from
being added to new jobs.
To fully remove a Campaign from GET listings and reports, you must instead "hide" the record. This marks the hidden
flag false.
You can un-hide a Campaign using the edit
endpoint.
HTTP Request
GET http://crm.oxifresh.com/campaigns/hide/<ID>
Query Parameters
Parameter | Description |
---|---|
ID | The ID of the campaign to hide |
Response
Parameter | Description |
---|---|
campaign | The "hidden" campaign data |
Activate a Campaign
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/campaigns/activate/4');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
baseRequest({
url: "https://crm.oxifresh.com/api/campaigns/activate/4",
method: "POST"
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"campaign": {
"id": 1,
"franchise_id": 2,
"campaigns_category_id": 10,
"campaigns_subcategory_id": 1,
"start_date": "2017-05-01T00:00:00+00:00",
"end_date": "",
"name": "Test Campaign",
"code": "TESTCODE",
"description": "Test campaign",
"cost": 0,
"last_modified": "2016-03-09T19:29:51+00:00",
"date_created": "2016-03-09T19:29:51+00:00",
"hidden": false,
"active": true
}
}
Opposite to delete
, marks the active
flag true.
HTTP Request
GET http://crm.oxifresh.com/campaigns/activate/<ID>
Query Parameters
Parameter | Description |
---|---|
ID | The ID of the campaign to activate (un-delete) |
Response
Parameter | Description |
---|---|
campaign | The "activated (un-deleted)" campaign data |
Get a breadcrumb trail
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/campaigns/breadcrumbs/4');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
baseRequest({
url: "https://crm.oxifresh.com/api/campaigns/breadcrumbs/4",
method: "POST"
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"breadcrumbs": [{
"id": 10,
"icon": "envelope-o",
"name": "Shared Mail",
"campaigns_categories_cost_type_id": 1,
"display_order": 3,
"active": true,
"type": "subcategories"
}, {
"id": 1,
"franchise_id": 2,
"campaigns_category_id": 10,
"name": "ValPak",
"active": true,
"hidden": 0,
"type": "campaigns"
}, {
"id": 1,
"franchise_id": 2,
"campaigns_category_id": 10,
"campaigns_subcategory_id": 1,
"start_date": "2018-01-01T00:00:00+00:00",
"end_date": "2018-03-01T00:00:00+00:00",
"name": "January Valpak 2018",
"code": "VP0110",
"description": "Mailed to 320,000 households on the second week of the month",
"cost": 5785,
"last_modified": "2016-03-09T19:29:51+00:00",
"date_created": null,
"hidden": false,
"active": true,
"selected": true
}],
"total": 3
}
Provides the hierarchy relationship of Campaign Category to Subcategory to Campaign.
HTTP Request
GET http://crm.oxifresh.com/campaigns/breadcrumnbs/<ID>
Query Parameters
Parameter | Description |
---|---|
ID | The ID of the campaign get breadcrumb hierarchy |
Response
Parameter | Description |
---|---|
breadcrumbs | The ordered listing of the hierarchy of Campaign Category, Subcategory, and Campaign |
total | The total number of items in the list |
Campaigns Categories
All Marketing Campaigns are listed under a top-level Campaign Category. These Categories are set and shared app-wide, so that reports can easily be pulled across franchises on the efficacy of certain types of marketing.
They can specify a Cost Source, indicating if the nested CampaignsCategories costs are manually entered or can be automatically determined by a different source (such as Royalty Fees).
List Campaigns Categories
<?php
$curl = curl_init();
$data = [
'franchise_id' => 2
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/campaignsCategories?'.$fieldsString);
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {franchsie_id: 2}
baseRequest({
url: "https://crm.oxifresh.com/api/campaignsCategories",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"campaignsCategories": [{
"count": 0,
"cost": null,
"id": 1,
"icon": "calendar-o",
"name": "Events",
"campaigns_categories_cost_type_id": 1,
"display_order": 9,
"active": true,
"campaigns_categories_cost_type": {
"id": 1,
"label": "Manual",
"description": "Source cost will be entered on each individual Campaign by the user.",
"handler": "Manual",
"options": "",
"active": true
}
}, {
"count": 50,
"cost": 21020.46,
"id": 2,
"icon": "laptop",
"name": "Internet",
"campaigns_categories_cost_type_id": 1,
"display_order": 2,
"active": true,
"campaigns_categories_cost_type": {
"id": 1,
"label": "Manual",
"description": "Source cost will be entered on each individual Campaign by the user.",
"handler": "Manual",
"options": "",
"active": true
}
}, {
"count": 17,
"cost": 900,
"id": 3,
"icon": "print",
"name": "Print",
"campaigns_categories_cost_type_id": 2,
"display_order": 4,
"active": true,
"campaigns_categories_cost_type": {
"id": 2,
"label": "NAF",
"description": "Source cost will be calculated from NAF fees",
"handler": "RoyaltyFee",
"options": "",
"active": true
}
}],
"total": 3
}
This endpoint gets a paginated list of CampaignsCategories.
HTTP Request
GET http://crm.oxifresh.com/campaignsCategories
Query Parameters
No options available at this time
Response
Only the standard pagination parameters are available for this endpoint.
Get a Campaign Category
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/campaignsCategories/3');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
var data = {franchsie_id: 2}
baseRequest({
url: "https://crm.oxifresh.com/api/campaignsCategories/3",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"campaignsCategory": {
"id": 3,
"icon": "calendar-o",
"name": "Events",
"campaigns_categories_cost_type_id": 1,
"display_order": 9,
"active": true,
"campaigns_categories_cost_type": {
"id": 1,
"label": "Manual",
"description": "Source cost will be entered on each individual Campaign by the user.",
"handler": "Manual",
"options": "",
"active": true
}
}
}
This endpoint retrieves a specific campaign category by ID.
HTTP Request
GET http://crm.oxifresh.com/campaignsCategories/<ID>
Query Parameters
Parameter | Description |
---|---|
ID | The ID of the campaign to retrieve |
Response
Parameter | Description |
---|---|
campaignCategory | The campaign category data |
Add A Campaign Category
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/campaignsCategories');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$data = [
'name' => 'New Category',
'icon' => 'user',
'campaigns_categories_cost_type_id' => 1,
'display_order' => 15
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $fieldsString );
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {
'name' : 'New Category',
'icon' : 'user',
'campaigns_categories_cost_type_id' : 1,
'display_order' : 15
}
baseRequest({
url: "https://crm.oxifresh.com/api/campaignsCategories",
method: "POST",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"campaignsCategory": {
"id": 4,
"icon": "user",
"name": "New Category",
"campaigns_categories_cost_type_id": 1,
"display_order": 15,
"active": true
}
}
HTTP Request
POST http://crm.oxifresh.com/campaignsCategories
Data Parameters
Parameter | Optional | Default | Description |
---|---|---|---|
name | No | null | The name of the campaign |
icon | No | null | The CSS class to use for the category icon |
campaigns_campaigns_categories_cost_type_id_id | No | null | The CampaignsCategoriesCostType ID this will reference for source costs |
campaigns_display_order_id | Yes | null | An ascending default display order for this category |
Response
Parameter | Description |
---|---|
campaignsCategory | The new campaign category data |
Edit A Campaign Category
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/campaignsCategories/4');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$data = [
'name' => 'Different Name'
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PATCH');
curl_setopt($curl, CURLOPT_POSTFIELDS, $fieldsString );
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {
'name': 'Different Name'
}
baseRequest({
url: "https://crm.oxifresh.com/api/campaignsCategories/4",
method: "PATCH",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"campaignsCategory": {
"id": 4,
"icon": "user",
"name": "Different Name",
"campaigns_categories_cost_type_id": 1,
"display_order": 15,
"active": true
}
}
Edits data in an existing Campaign Category. You only need to supply fields you wish to change, but you can optionally supply all of the fields.
HTTP Request
PATCH http://crm.oxifresh.com/campaignsCategories/<ID>
Data Parameters
Parameter | Optional | Default | Description |
---|---|---|---|
name | No | Yes | The name of the campaign |
icon | No | Yes | The CSS class to use for the category icon |
campaigns_campaigns_categories_cost_type_id_id | Yes | null | The CampaignsCategoriesCostType ID this will reference for source costs |
campaigns_display_order_id | Yes | null | An ascending default display order for this category |
Response
Parameter | Description |
---|---|
campaignsCategory | The edited campaign category data |
Delete a Campaign Category
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/campaignsCategories/delete/4');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
baseRequest({
url: "https://crm.oxifresh.com/api/campaignsCategories/4",
method: "DELETE"
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"campaignsCategory": {
"id": 4,
"icon": "user",
"name": "Different Name",
"campaigns_categories_cost_type_id": 1,
"display_order": 15,
"active": false
}
}
This endpoint deletes a specific campaign cateogry by ID. Note that it's a "soft delete" - marking
the active
flag false.
HTTP Request
DELETE http://crm.oxifresh.com/campaignsCategories/delete/<ID>
Query Parameters
Parameter | Description |
---|---|
ID | The ID of the campaign category to delete |
Response
Parameter | Description |
---|---|
campaign | The "deleted" campaign category data |
Campaign Category Breadcrumb
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/campaignsCategories/breadcrumbs/1');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
baseRequest({
url: "https://crm.oxifresh.com/api/campaignsCategories/breadcrumbs/1",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"breadcrumbs": [{
"id": 1,
"icon": "calendar-o",
"name": "Events",
"campaigns_categories_cost_type_id": 1,
"display_order": 9,
"active": true,
"selected": true
}],
"total": 1
}
Provides the breadcrumb compatible hierarchy relationship of Campaign Category. Note that all of the Campaign-related controllers implement this, generally as an agnostic view of where each piece is in the hierarchy.
HTTP Request
GET http://crm.oxifresh.com/campaignsCategories/breadcrumnbs/<ID>
Query Parameters
Parameter | Description |
---|---|
ID | The ID of the campaign category to get a breadcrumb hierarchy |
Response
Parameter | Description |
---|---|
breadcrumbs | The listing of the hierarchy of Campaign Category |
total | The total number of items in the list |
Categories
All services and products specify a Category that helps group similar items in reports. Categories are specified at an app level and are shared across all franchises.
Get a list of categories
<?php
$curl = curl_init();
$data = [
'franchise_id' => 2
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/categories?'.$fieldsString);
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if ($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {franchsie_id: 2}
baseRequest({
url: "https://crm.oxifresh.com/api/categories",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"categories": [
{
"id": 1,
"type": "SERVICE",
"code": "CPT",
"name": "Carpet",
"description": null,
"icon_id": 2,
"scheduling_exempt": 0,
"display_order": 1,
"include_in_exports": 1,
"active": true
},
{
"id": 2,
"type": "SERVICE",
"code": "UPHOL",
"name": "Upholstery",
"description": null,
"icon_id": 1,
"scheduling_exempt": 0,
"display_order": 2,
"include_in_exports": 1,
"active": true
},
{
"id": 3,
"type": "SERVICE",
"code": "T\u0026G",
"name": "Tile \u0026 Grout",
"description": "test ",
"icon_id": 3,
"scheduling_exempt": 0,
"display_order": 6,
"include_in_exports": 1,
"active": true
}
],
"total": 3,
"page": 1,
"limit": 20
}
This endpoint gets a paginated list of Categories.
HTTP Request
GET http://crm.oxifresh.com/categories
Query Parameters
Only the standard pagination parameters are available for this endpoint.
Response
Parameter | Description |
---|---|
categories | A paginated list of categories |
Get a Specific Category
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/categories/1');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
baseRequest({
url: "https://crm.oxifresh.com/api/categories/1",
method: "GET"
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success
}
}
)
This will return JSON results structured like this:
{
"category": {
"id": 1,
"type": "SERVICE",
"code": "CPT",
"name": "Carpet",
"description": null,
"icon_id": 2,
"scheduling_exempt": 0,
"display_order": 1,
"include_in_exports": 1,
"active": true,
"services": [
{
"id": 1,
"category_id": 1,
"code": "RM",
"name": "Room",
"description": "",
"display_order": 1,
"auto_add_to_quote": false,
"hidden": false,
"active": true,
"last_modified": "2017-11-13T14:51:02+00:00"
},
{
"id": 2,
"category_id": 1,
"code": "STR",
"name": "Staircase - Up to 16 steps",
"description": "",
"display_order": 3,
"auto_add_to_quote": false,
"hidden": false,
"active": true,
"last_modified": "2017-11-13T14:51:23+00:00"
}
]
}
}
This endpoint retrieves a specific category by ID, and includes a list of all the Services listed under it.
Note that while Services are specified at an app level, each franchise sets their own pricing, duration, and descriptions in a linked 'ServicesSetting' record. The 'Service Setting' ID is what is listed on each Invoice Line Item.
HTTP Request
GET http://crm.oxifresh.com/categories/<ID>
Query Parameters
Parameter | Description |
---|---|
ID | The ID of the category to retrieve |
Response
Parameter | Description |
---|---|
category | The category data, including it's Services |
Add a new Category
<?php
$data = [
"type" => "SERVICE",
"code" => "SPR",
"name" => "Sprockets",
"display_order" => 20
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/categories');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $fieldsString);
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {
"id": 1,
"type": "SERVICE",
"code": "SPR",
"name": "Sprockets",
"display_order": 20
}
baseRequest({
url: "https://crm.oxifresh.com/api/categories",
method: "POST",
body: data
},
function (error, response, body) {
if (!error && response.statusCode === 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"category": {
"id": 20,
"type": "SERVICE",
"code": "SPR",
"name": "Sprockets",
"description": null,
"icon_id": null,
"scheduling_exempt": 0,
"display_order": 20,
"include_in_exports": 0,
"active": true
}
}
This endpoint creates a new Category.
HTTP Request
POST http://crm.oxifresh.com/categories
Query Parameters
Parameter | Required | Description |
---|---|---|
type | Yes | Specifies what kind of Category this represents (either 'SERVICE' or 'PRODUCT') |
name | Yes | The full display name of the category |
code | No | A short display code, usually 3-4 characters but up to 10 |
description | No | An optional helpful description |
display_order | Yes | An numerical value to help order the Category listings in the app |
scheduling_exempt | No | A boolean value which will indicate the Services listed under this do not impact Schedules, and therefore will not be shown in the Scheduling settings. Defaults to false |
include_in_exports | No A boolean value which indicates if the automated exports system should include these services in the export. Defaults to false. | |
icon_id | No | A legacy field which used to correspond to an icon image, not in use |
Response
Parameter | Description |
---|---|
category | The new category. |
Edit a Category
<?php
$data = [
"code" => "SPRTS"
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/categories/20');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PATCH');
curl_setopt($curl, CURLOPT_POSTFIELDS, $fieldsString );
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {
"code": "SPRTS",
}
baseRequest({
url: "https://crm.oxifresh.com/api/categories/20",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode === 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"category": {
"id": 20,
"type": "SERVICE",
"code": "SPRTS",
"name": "Sprockets",
"description": null,
"icon_id": null,
"scheduling_exempt": 0,
"display_order": 20,
"include_in_exports": 0,
"active": true
}
}
This endpoint edits an existing Category.
HTTP Request
PATCH http://crm.oxifresh.com/categories/<ID>
Query Parameters
Will accept all the same parameters as the add POST
endpoint. Note that you only need to include the fields you wish to change.
Response
Parameter | Description |
---|---|
category | The deleted category. |
Delete a Category
<?php
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/categories/20');
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {
"code": "SPRTS"
}
baseRequest({
url: "https://crm.oxifresh.com/api/categories/api/delete/20",
method: "DELETE",
body: data
},
function (error, response, body) {
if (!error && response.statusCode === 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"category": {
"id": 20,
"type": "SERVICE",
"code": "SPRTS",
"name": "Sprockets",
"description": null,
"icon_id": null,
"scheduling_exempt": 0,
"display_order": 20,
"include_in_exports": 0,
"active": false
}
}
Soft-deletes a Category by setting it's active
flag to false
HTTP Request
DELETE http://crm.oxifresh.com/categories/<ID>
Response
Parameter | Description |
---|---|
category | The edited category. |
Coupon Validation
Applicability & Discounts
<?php
$curl = curl_init();
// Pass a JSON-encoded string of line items data
$lineItemsJson = [
[
'invoice_id' => null,
'package_id' => null,
'service_id' => 14421, // Room
'product_id' => null,
'coupon_id' => null,
'quantity' => 1,
'notes' => 'hey',
'price' => 30.00,
'discount' => 0.0,
'taxable' => false,
'type' => 1,
'unit_quantity' => null,
'priced_on' => 'per_item',
'tax' => 0.0,
'active' => true
],
[
'invoice_id' => null,
'package_id' => null,
'service_id' => null,
'product_id' => null,
'coupon_id' => 114, // Coupon for 2 Rooms for $58
'quantity' => 1,
'notes' => '',
'price' => 0.00,
'discount' => 0.0,
'taxable' => false,
'type' => 1,
'unit_quantity' => null,
'priced_on' => 'per_item',
'tax' => 0.0,
'active' => true
],
];
$data = [
'line_items' => json_encode($lineItemsJson)
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/couponsValidator/validate');
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $fieldsString );
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = [
{
"invoice_id": null,
"package_id": null,
"service_id": 14421,
"product_id": null,
"coupon_id": null,
"quantity": 1,
"notes": "hey",
"price": 30,
"discount": 0,
"taxable": false,
"type": 1,
"unit_quantity": null,
"priced_on": "per_item",
"tax": 0,
"active": true
}, {
"invoice_id": null,
"package_id": null,
"service_id": null,
"product_id": null,
"coupon_id": 114,
"quantity": 1,
"notes": "",
"price": 0,
"discount": 0,
"taxable": false,
"type": 1,
"unit_quantity": null,
"priced_on": "per_item",
"tax": 0,
"active": true
}
];
baseRequest({
url: "https://crm.oxifresh.com/api/campaignsCategories",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
The above command returns JSON with two top level nodes, overall validity of coupons listed and validation results for each individual line item (in the same order as provided). It will return JSON structured like this:
{
"valid": false,
"results": [
{
"invoice_line_item": {
"invoice_id": null,
"quantity": 1,
"notes": "1",
"taxable": false,
"tax": 0,
"discount": 0,
"price": 30,
"priced_on": "per_item",
"service_id": 14421,
"product_id": null,
"coupon_id": null,
"active": true,
"services_setting": {
"id": 14421,
"franchise_id": 501,
"category_id": 1,
"code": "",
"name": "Room",
"description": "1 Room = 250 sq. ft.",
"price": 30,
"duration": 1800,
"unit": "",
"price_per_unit": null,
"priced_on": "per_item",
"taxable": true,
"service_id": 1,
"last_modified": "2016-11-09T16:15:01+00:00",
"active": true,
"category": {
"id": 1,
"type": "SERVICE",
"code": "CPT",
"name": "Carpet",
"description": null,
"icon_id": 2,
"scheduling_exempt": 0,
"display_order": 1,
"include_in_exports": 0,
"active": true
}
},
"category": {
"id": 1,
"type": "SERVICE",
"code": "CPT",
"name": "Carpet",
"description": null,
"icon_id": 2,
"scheduling_exempt": 0,
"display_order": 1,
"include_in_exports": 0,
"active": true
},
"total": 30,
"category_name": "Carpet",
"category_code": "CPT"
}
},
{
"invoice_line_item": {
"invoice_id": null,
"quantity": 1,
"notes": "1",
"taxable": false,
"tax": 0,
"discount": 0,
"price": 0,
"priced_on": "per_item",
"service_id": null,
"product_id": null,
"coupon_id": 114,
"active": true,
"total": 0,
"category_name": null,
"category_code": null
},
"coupon": {
"id": 114,
"franchise_id": 501,
"coupons_pack_id": 1,
"campaign_id": null,
"name": "2 RM 4 $50",
"description": "",
"start_date": "2017-10-11T00:00:00+00:00",
"expiration_date": null,
"created_by_employee_id": 1,
"last_modified_by_employee_id": 1,
"date_created": "2017-10-11T12:22:35+00:00",
"last_modified": "2017-10-11T12:22:35+00:00",
"cannot_be_combined": true,
"list_order": 0,
"active": true,
"coupons_rules": [
{
"id": 1,
"coupon_id": 114,
"description": null,
"is_set_wide_requirement": false,
"is_tiered": true,
"tier_order": 1,
"coupons_rules_type_id": 1,
"value": 50,
"discount_type": "flat",
"compound_type": "all",
"active": true,
"coupons_rules_items": [
{
"id": 1,
"coupons_rule_id": 1,
"entity_type": "Categories",
"entity_id": 1,
"value": 50,
"type": "min_amount",
"has_substitutes": false,
"substitution_type": "or",
"primary_discount_item": true,
"active": true,
"coupons_rules_items_substitutes": [],
"item": {
"id": 1,
"type": "SERVICE",
"code": "CPT",
"name": "Carpet",
"description": null,
"icon_id": 2,
"scheduling_exempt": 0,
"display_order": 1,
"include_in_exports": 0,
"active": true
},
"item_name": "Carpet"
}
]
},
{
"id": 2,
"coupon_id": 114,
"description": null,
"is_set_wide_requirement": false,
"is_tiered": true,
"tier_order": 2,
"coupons_rules_type_id": 1,
"value": 10,
"discount_type": "dollar",
"compound_type": "all",
"active": true,
"coupons_rules_items": [
{
"id": 2,
"coupons_rule_id": 2,
"entity_type": "ServicesSettings",
"entity_id": 14395,
"value": 2,
"type": "min_quantity",
"has_substitutes": false,
"substitution_type": "or",
"primary_discount_item": true,
"active": true,
"coupons_rules_items_substitutes": [
{
"id": 1,
"coupons_rules_item_id": 2,
"entity_type": "Categories",
"entity_id": 1,
"value": 60,
"type": "min_amount",
"active": true,
"item": {
"id": 1,
"type": "SERVICE",
"code": "CPT",
"name": "Carpet",
"description": null,
"icon_id": 2,
"scheduling_exempt": 0,
"display_order": 1,
"include_in_exports": 0,
"active": true
},
"item_name": "Carpet"
}
],
"item": {
"id": 14395,
"franchise_id": 501,
"category_id": 1,
"code": "",
"name": "Room",
"description": "1 Room = 250 sq. ft.",
"price": 30,
"duration": 1800,
"unit": "",
"price_per_unit": null,
"priced_on": "per_item",
"taxable": true,
"service_id": 1,
"last_modified": "2016-11-09T16:15:01+00:00",
"active": true
},
"item_name": "Room"
}
]
}
],
"expired": false
},
"valid": false,
"missing": [
{
"rule": {
"id": 2,
"coupon_id": 114,
"description": null,
"is_set_wide_requirement": false,
"is_tiered": true,
"tier_order": 2,
"coupons_rules_type_id": 1,
"value": 10,
"discount_type": "dollar",
"compound_type": "all",
"active": true
},
"missing_items": [
{
"missing_amount": 1,
"item_price": 60,
"item": {
"id": 2,
"coupons_rule_id": 2,
"entity_type": "ServicesSettings",
"entity_id": 14395,
"value": 2,
"type": "min_quantity",
"has_substitutes": false,
"substitution_type": "or",
"primary_discount_item": true,
"active": true,
"coupons_rules_items_substitutes": [
{
"id": 1,
"coupons_rules_item_id": 2,
"entity_type": "Categories",
"entity_id": 1,
"value": 60,
"type": "min_amount",
"active": true,
"item_name": "Carpet"
}
],
"item": {
"id": 14395,
"franchise_id": 501,
"category_id": 1,
"code": "",
"name": "Room",
"description": "1 Room = 250 sq. ft.",
"price": 30,
"duration": 1800,
"unit": "",
"price_per_unit": null,
"priced_on": "per_item",
"taxable": true,
"service_id": 1,
"last_modified": "2016-11-09T16:15:01+00:00",
"active": true
},
"item_name": "Room"
},
"substitutes": [
{
"missing_amount": 30,
"item_price": 60,
"item": {
"id": 1,
"coupons_rules_item_id": 2,
"entity_type": "Categories",
"entity_id": 1,
"value": 60,
"type": "min_amount",
"active": true,
"item": {
"id": 1,
"type": "SERVICE",
"code": "CPT",
"name": "Carpet",
"description": null,
"icon_id": 2,
"scheduling_exempt": 0,
"display_order": 1,
"include_in_exports": 0,
"active": true
},
"item_name": "Carpet"
}
}
]
}
]
},
{
"rule": {
"id": 1,
"coupon_id": 114,
"description": null,
"is_set_wide_requirement": false,
"is_tiered": true,
"tier_order": 1,
"coupons_rules_type_id": 1,
"value": 50,
"discount_type": "flat",
"compound_type": "all",
"active": true
},
"missing_items": [
{
"missing_amount": 20,
"item_price": 50,
"item": {
"id": 1,
"coupons_rule_id": 1,
"entity_type": "Categories",
"entity_id": 1,
"value": 50,
"type": "min_amount",
"has_substitutes": false,
"substitution_type": "or",
"primary_discount_item": true,
"active": true,
"coupons_rules_items_substitutes": [],
"item_name": "Carpet"
}
}
]
}
]
}
]
}
When valid, each discounted item will list a discounts node, specifying the amount of items discounted (specified either as a quantity or price), and the 'discount' amount in dollars:
{
"invoice_line_item": {
"invoice_id": null,
"quantity": 2,
"notes": "1",
"taxable": false,
"tax": 0,
"discount": 0,
"price": 30,
"priced_on": "per_item",
"service_id": 14395,
"product_id": null,
"coupon_id": null,
"active": true,
"total": 60,
"category_name": "Carpet",
"category_code": "CPT"
},
"discounts": [
{
"id": 0,
"amount_discounted": 2,
"discount_amount_type": "quantity",
"discount": 10
}
]
}
This endpoint validates that coupons apply (or don't apply) in a list of line items, and calculates discount amounts for items discounted by coupons.
HTTP Request
GET http://crm.oxifresh.com/couponsValidator/validate
Query Parameters
Parameter | Description |
---|---|
line_items | A JSON-encoded string of line items data, including any Coupons to validate as line items themselves. |
Response
Parameter | Description |
---|---|
valid | True if all coupons are valid or if no Coupons are listed |
results | A list of the line items provided but decorated with discounts, or with Coupon applicability information. See below for detailed explanations |
Service/Product Results
Each-non Coupon line item will list a 'discounts' node, if the line item was automatically discounted.
See an example on the right.
Parameter | Description |
---|---|
discounts | An array of discount information. Lists how many items were applicable in amount_discounted , the type used in discount_amount_type (quantity or price ) and the total $ amount to take off in discount . |
Coupon Results
Note that each coupon listed in line_items will list the following in results:
Parameter | Description |
---|---|
coupon | The extended rules, items, and substitutes listed for that Coupon ID. |
valid | If the coupon applied, it was 'valid'. If it's not valid, you may need to remove it or prompt the user to add additional items to make it valid. |
missing | If not valid, it will list a node for each Rule that wasn't valid, containing a list of what items are required to add to make it valid. |
Generally, it's expected you'll loop through the missing nodes to build suggestions on what services the customer can add to make the coupon apply.
In the example above, results[1][missing][0][missing_items][0][missing_amount]
shows it's missing 1 of the item,
whose name is found in results[1][missing][0][missing_items][0][item][item_name]
, "Room".
It lists a substitute for the missing Room, results[1][missing][0][missing_items][0][substitutes][0][item][item_name]
any Category "Carpet", totaling
results[1][missing][0][missing_items][0][substitutes][0][missing_amount]
, $30 (the type is 'min_amount' so it's referring to dollars rather than Quantities)
Employees
Employees are the application's user accounts.
Get a list of employees
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/employees?franchise_id=501');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
This will return JSON results structured like this:
{
"employees": [
{
"id": 4,
"franchise_id": 501,
"group_id": 7,
"title": "",
"first_name": "TestFranchise",
"middle_initial": "",
"last_name": "Owner",
"suffix": "",
"address_1": "575 Lincoln St",
"address_2": "",
"city": "Denver",
"state": "CO",
"zipcode": 80203,
"telephone": "7205322733",
"mobile": "3035551234",
"email": "example+testing@oxifresh.com",
"start_date": null,
"birth_date": null,
"username": "testingfranchiseowner",
"color": "000000",
"ext_user_id": "",
"schedule": true,
"bridge_controlled": false,
"verified_email": false,
"display_order": 1,
"active": true,
"group": {
"id": 7,
"name": "Franchise Owner",
"corporate": false,
"admin_rank": 70,
"display_order": 2,
"active": true
}
},
{
"id": 5,
"franchise_id": 501,
"group_id": 5,
"title": null,
"first_name": "TestFranchise",
"middle_initial": "",
"last_name": "Tech",
"suffix": null,
"address_1": "575 Lincoln St",
"address_2": "",
"city": "Denver",
"state": "CO",
"zipcode": 80203,
"telephone": "7205322732",
"mobile": "3035551234",
"email": "example+devtest_TestFranchise@oxifresh.com",
"start_date": null,
"birth_date": null,
"username": "testingfranchisetech",
"color": "1eba2d",
"ext_user_id": null,
"schedule": true,
"bridge_controlled": false,
"verified_email": false,
"display_order": 3,
"active": true,
"group": {
"id": 5,
"name": "Techs",
"corporate": false,
"admin_rank": 40,
"display_order": 1,
"active": true
}
}
],
"total": 3
}
This endpoint gets a paginated list of Employees.
HTTP Request
GET http://crm.oxifresh.com/employees
Query Parameters
Parameter | Default | Description |
---|---|---|
search | null | A generic search term that will return employees with matching employee names, usernames, emails or phone numbers |
Response
Parameter | Description |
---|---|
employees | A paginated list of employees, includes the linked Group, and for all-franchise lists the linked Franchise |
total | The total number of employees matching the input criteria |
Get a Specific Employee
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/employees/4');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
This will return JSON results structured like this:
{
"employee": {
"id": 4,
"franchise_id": 501,
"group_id": 7,
"title": "",
"first_name": "TestFranchise",
"middle_initial": "",
"last_name": "Owner",
"suffix": "",
"address_1": "575 Lincoln St",
"address_2": "",
"city": "Denver",
"state": "CO",
"zipcode": 80203,
"telephone": "7205322733",
"mobile": "3035551234",
"email": "example+testing@oxifresh.com",
"start_date": null,
"birth_date": null,
"username": "testingfranchiseowner",
"color": "000000",
"ext_user_id": "",
"schedule": true,
"bridge_controlled": false,
"verified_email": false,
"display_order": 1,
"active": true,
"group": {
"id": 7,
"name": "Franchise Owner",
"corporate": false,
"admin_rank": 70,
"display_order": 2,
"active": true
}
}
}
This endpoint retrieves a employee by ID.
HTTP Request
GET http://crm.oxifresh.com/employees/<ID>
Query Parameters
Parameter | Description |
---|---|
ID | The ID of the employee to retrieve |
Response
Parameter | Description |
---|---|
employee | The employee data |
Add A Employee
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/employees');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$data = [
'franchise_id' => 501,
'first_name' => 'John',
'last_name' => 'Smith',
'group_id' => 5,
'username' => 'john.smith'
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $fieldsString );
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
This will return JSON results structured like this:
{
"employee": {
"id": 6,
"franchise_id": 501,
"group_id": 5,
"title": "",
"first_name": "John",
"middle_initial": "",
"last_name": "Smith",
"suffix": "",
"address_1": "",
"address_2": "",
"city": "",
"state": "",
"zipcode": "",
"telephone": "",
"mobile": "",
"email": "",
"start_date": null,
"birth_date": null,
"username": "john.smith",
"color": "000000",
"ext_user_id": "",
"schedule": false,
"bridge_controlled": false,
"verified_email": false,
"display_order": 1,
"active": true,
"group": {
"id": 5,
"name": "Techs",
"corporate": false,
"admin_rank": 50,
"display_order": 3,
"active": true
}
}
}
This endpoint adds a new Employee to the system.
HTTP Request
POST http://crm.oxifresh.com/employees
Data Parameters
Note that you may pass values for any of the fields listed in GET/<id>
, but the following are required.
Parameter | Optional | Default | Description |
---|---|---|---|
first_name* | No | null | First Name |
last_name* | Yes | null | Last Name |
username* | Yes | null | A unique login username |
group_id* | Yes | null | The ID for the Group to assign this user to. Note that you won't be able to create users in Groups above your own Admin Rank |
*Required
Response
Parameter | Description |
---|---|
employee | The new employee data. |
Delete an Employee
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/employees/4');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
This will return JSON results structured like this:
{
"employee": {
"id": 4,
"franchise_id": 501,
"group_id": 7,
"title": "",
"first_name": "TestFranchise",
"middle_initial": "",
"last_name": "Owner",
"suffix": "",
"address_1": "575 Lincoln St",
"address_2": "",
"city": "Denver",
"state": "CO",
"zipcode": 80203,
"telephone": "7205322733",
"mobile": "3035551234",
"email": "example+testing@oxifresh.com",
"start_date": null,
"birth_date": null,
"username": "testingfranchiseowner",
"color": "000000",
"ext_user_id": "",
"schedule": true,
"bridge_controlled": false,
"verified_email": false,
"display_order": 1,
"active": false
}
}
This endpoint deletes a specific Employee by ID. Note that it's a "soft delete" - marking the active
flag false. This
removes it from GET
lists and aggregate employees, and prevents the user from logging in.
Note that they will be visible in the Calendar until you set schedule
to false as well.
HTTP Request
DELETE http://crm.oxifresh.com/employees/<ID>
Query Parameters
Parameter | Description |
---|---|
ID | The ID of the employee to delete |
Response
Parameter | Description |
---|---|
employee | The "deleted" employee data |
Invoices
All jobs are linked to a single entity technically called an 'invoice'.
These invoices have a invoices_status_id
which indicates if the record represents a:
- Quote - an estimate on potential work
- **Work Order - a scheduled job
- Invoice - a completed job edited to include any upsell items
- Paid - payments have been applied and the invoice is locked
- Voided - Job or Quote was cancelled
The IDs for these statuses can be pulled from the invoicesStatuses
endpoint.
The status ID is modified as the record progressed from one state to another, usually ending as either Paid or Voided.
These 'invoice' records are locked once they reach a Paid state, and can't be modified unless reverted back to a Work Order.
Get a list of invoices
<?php
$curl = curl_init();
$data = [
'franchise_id' => 2
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/invoices?'.$fieldsString);
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {franchsie_id: 2}
baseRequest({
url: "https://crm.oxifresh.com/api/invoices",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"invoices": [
{
"id": 885632,
"franchise_id": 2,
"invoice_status_id": 2,
"contact_id": 912099,
"employee_id": 4151,
"work_address_id": 965678,
"campaign_id": 10896,
"location_id": 2,
"sequence": 28374,
"po_number": null,
"created_by_id": 2764,
"date_created": "2017-10-03T12:00:03+00:00",
"last_modified": "2017-10-03T06:00:03+00:00",
"date_last_exported": null,
"date_due": "2017-10-03T12:00:03+00:00",
"date_quoted": "2017-10-03T12:00:03+00:00",
"date_completed": "2017-11-01T12:00:00+00:00",
"date_paid": null,
"notes": "Booked Online",
"subtotal": 270,
"tax_percent": 0.074999999999999997,
"tax_due": 0,
"amount_due": 270,
"amount_paid": 0,
"original_subtotal": 0,
"quote_confirmed": false,
"imported": false,
"active": true,
"invoices_campaigns": [
{
"id": 18629,
"franchise_id": 2,
"invoice_id": 885632,
"campaign_id": 10896,
"no_code_explanation": null,
"date_created": "2017-10-05T10:34:42+00:00",
"last_modified": "2017-10-05T10:34:42+00:00",
"created_by_employee_id": 2764,
"last_modified_by_employee_id": 2764,
"active": true,
"campaign": {
"id": 10896,
"franchise_id": 2,
"coupons_pack_id": 2,
"campaigns_category_id": 2,
"campaigns_subcategory_id": 31,
"start_date": "2012-05-01T00:00:00+00:00",
"end_date": null,
"name": "OS2500",
"code": "OS2500",
"description": "",
"cost": null,
"last_modified": "2017-10-10T17:36:36+00:00",
"date_created": null,
"hidden": false,
"active": true
}
}
],
"address": {
"id": 965678,
"entity_type": 4,
"entity_id": 912099,
"template_id": 1,
"type_id": 1,
"template_group_id": null,
"address1": "15296 W Warren Ave",
"address2": null,
"unit_type": null,
"unit_number": "",
"county": null,
"city": "Denver",
"state": "CO",
"zipcode": "80228",
"country_id": 1,
"active": 1,
"latitude": null,
"longitude": null,
"unit_type_name": null,
"full_street": "15296 W Warren Ave",
"country_code": "US",
"type_name": null
},
"contact": {
"id": 912099,
"franchise_id": 2,
"type_id": 1,
"classification_id": 1,
"status_id": 2,
"campaign_id": null,
"title": null,
"first_name": "Anita ",
"middle_initial": null,
"last_name": "Ritchie",
"suffix": null,
"date_of_birth": null,
"gender": null,
"telephone": "8322283443",
"telephone2": null,
"telephone3": null,
"telephone4": null,
"email": "anitaritchie@icloud.com",
"url": null,
"account_number": null,
"company_name": null,
"date_created": "2017-10-03T12:00:03+00:00",
"last_modified": "2017-10-03T06:00:03+00:00",
"date_called": null,
"hide": false,
"active": true,
"unsubscribed": false,
"constant_contact_id": null,
"full_name": "Anita Ritchie"
},
"category_ids": [
1,
5
],
"is_completed": false
}
],
"total": 2358,
"page": 1,
"limit": 1
}
This endpoint gets a paginated list of Invoices. It can be sorted and filtered by passing extra parameters, outlined below.
Filtering & Date Limiting
Whenever an Invoice or it's line items are updated, it's last_modified
field is updated. You can limit results to
those updated after a specific date with the last_modified_after
parameter.
Optionally, it can return a list of the invoice's line item's Category IDs by passing include_categories
.
You can dereference these using the categories
API endpoint.
Similarly, you can request a list of the Campaigns linked to each invoice with include_campaigns
, and even filter results to
those that match a specific Campaign code with campaign_code
.
Bulk Listings
The default page size is just 10 records, but you can increase that limit significantly with limit
.
If the limit is too high, requests can timeout or overflow and the API will return a 500-coded response.
HTTP Request
GET http://crm.oxifresh.com/invoices
Query Parameters
Parameter | Default | Example | Description |
---|---|---|---|
last_modified_after | null | 2018-03-01 17:52:12 | Restricts list to those that have been updated after the date specified (formatted YYYY-MM-DD or YYYY-MM-DD HH:MM:SS). Time is UTC. |
date_created_after | null | 2018-03-01 17:52:12 | Set to true to include invoices that were not indicated to be shown publicly |
date_completed_after | null | 2018-03-01 17:52:12 | Restricts results to those who are scheduled to be completed after the specified date |
include_categories | null | true | Include a list of the unique Category IDs this invoice has listed in it's line items |
include_summary | false | true | Include a human-readable summary of the total & invoice line items |
include_campaigns | false | true | Include a list of the Invoice Campaign records and their linked Campaign |
campaign_code | null | OS2500 | Filter list to jobs that were associated with the specified Marketing Campaign code |
contact_id | null | 22456 | Limit to only invoices that match the specified contact ID |
Response
Parameter | Description |
---|---|
invoices | A paginated list of invoices, includes the Contact and service Address records by default. |
Invoice Response Details
Each of invoices
will contain the following fields:
Parameter | Example | Description |
---|---|---|
franchise_id | 2 | The franchise ID this invoice belongs to (immutable) |
invoice_status_id | 2 | The type of invoice (Quote/Work Order/Invoice/Paid/Void) |
contact_id | 912099 | The ID of the Contact this invoice is for |
employee_id | 4151 | The ID of the Employee that is assigned to do this work |
work_address_id | 965678 | The ID of the Address the job is scheduled to take place |
campaign_id | 10896 | Deprecated - This will be removed soon, the same information is now available in the invoices_campaigns parameter |
location_id | 2 | The ID of the Location this job is located in. Can be changed until date_last_exported is set. |
sequence | 28374 | The auto-incremented number of the invoice, unique to the Franchise (ie. all franchises usually start at 1) |
po_number | null | A PO Number, not currently in use |
created_by_id | 2764 | The ID of the Employee that created this Invoice |
date_created | "2017-10-03T12:00:03+00:00" | The date it was created |
last_modified | "2017-10-03T06:00:03+00:00" | The date it was last modified, or it's Invoice Line Items were modified |
date_last_exported | null | Date it was last exported to a 3rd party vendor. If not null, Location ID becomes Immutable. |
date_due | "2017-10-03T12:00:03+00:00" | Date payment is due |
date_quoted | "2017-10-03T12:00:03+00:00" | Date the Quote (if applicable) was created |
date_completed | "2017-11-01T12:00:00+00:00" | Date the job was actually done (should syncronize with it's Appointment datetime) |
date_paid | null | Date the invoice was marked Paid |
notes | "Booked Online" | Any custom notes entered manually |
subtotal | 270 | Pre-tax subtotal |
tax_percent | 0.074999999999999997 | The tax rate |
tax_due | 0 | The effect amount of tax due (note that some line items may not be taxable, so this is not necessarily the subtotal times the tax_percent ) |
amount_due | 270 | The post-tax total amount, due in full |
amount_paid | 0 | The total of any payments made on this invoice |
original_subtotal | 0 | A record of the pre-tax subtotal when the job was originally booked as a Work Order (the final total may be different, the effective difference is the 'Upsell amount') |
quote_confirmed | false | A flag which when false indicates edits to subtotal should update original_subtotal |
imported | false | A flag to indicate the invoice was imported from a 3rd party source |
active | true | If false, the record is treated as if it were hard-deleted |
is_completed | true | Simplified flag to indicate if the invoice work has been marked as closed out and "completed" |
It will also list any of the following foreign-keyed associations, depending on what data was requested:
Parameter | Description |
---|---|
address | The service Address for the job, includes the street address information & geocode |
contact | The Contact record, with their name, phone, email |
category_ids | If include_categories was specified, an array of the distinct Category IDs that made up the line items |
summary | If include_summary was specified, a human-readable summary of the job including the total and a list of service quantites and codes |
invoices_campaigns | If include_campaigns was specified, an array of all the linked Invoice Campaigns, which in turn list the marketing Campaign |
Get a Specific Invoice
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/invoices/1');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
baseRequest({
url: "https://crm.oxifresh.com/api/invoices/1",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"invoice": {
"id": 885632,
"franchise_id": 2,
"invoice_status_id": 2,
"contact_id": 912099,
"employee_id": 4151,
"work_address_id": 965678,
"campaign_id": 10896,
"location_id": 2,
"sequence": 28374,
"po_number": null,
"created_by_id": 2764,
"date_created": "2017-10-03T12:00:03+00:00",
"last_modified": "2017-10-03T06:00:03+00:00",
"date_last_exported": null,
"date_due": "2017-10-03T12:00:03+00:00",
"date_quoted": "2017-10-03T12:00:03+00:00",
"date_completed": "2017-11-01T12:00:00+00:00",
"date_paid": null,
"notes": "Booked Online",
"subtotal": 270,
"tax_percent": 0.074999999999999997,
"tax_due": 0,
"amount_due": 270,
"amount_paid": 0,
"original_subtotal": 0,
"quote_confirmed": false,
"imported": false,
"active": true,
"invoices_campaigns": [
{
"id": 18629,
"franchise_id": 2,
"invoice_id": 885632,
"campaign_id": 10896,
"no_code_explanation": null,
"date_created": "2017-10-05T10:34:42+00:00",
"last_modified": "2017-10-05T10:34:42+00:00",
"created_by_employee_id": 2764,
"last_modified_by_employee_id": 2764,
"active": true,
"campaign": {
"id": 10896,
"franchise_id": 2,
"coupons_pack_id": null,
"campaigns_category_id": 10,
"campaigns_subcategory_id": 1,
"start_date": "2018-01-01T00:00:00+00:00",
"end_date": null,
"name": "Feb Valpak 2018",
"code": "VP0218",
"description": "Sent to all the cool kids",
"cost": 1337,
"last_modified": "2018-03-19T12:29:51+00:00",
"date_created": "2018-03-09T12:29:51+00:00",
"hidden": false,
"active": false
}
}
],
"invoice_line_items": [
{
"id": 3959184,
"invoice_id": 885632,
"package_id": null,
"service_id": null,
"product_id": null,
"coupon_id": 522,
"quantity": 1,
"notes": "",
"price": 0,
"discount": 0,
"taxable": false,
"tax": 0,
"overridden_by_employee_id": null,
"overridden_date": null,
"active": true,
"total": 0,
"category_name": null,
"category_code": null,
"name": null
},
{
"id": 3959185,
"invoice_id": 885632,
"package_id": null,
"service_id": 3911,
"product_id": null,
"coupon_id": null,
"quantity": 5,
"notes": "",
"price": 45,
"discount": -67,
"taxable": false,
"tax": 0,
"overridden_by_employee_id": null,
"overridden_date": null,
"active": true,
"total": 158,
"category_name": "Carpet",
"category_code": "CPT",
"name": "Room"
},
{
"id": 3959186,
"invoice_id": 885632,
"package_id": null,
"service_id": 3921,
"product_id": null,
"coupon_id": null,
"quantity": 2,
"notes": "",
"price": 50,
"discount": 0,
"taxable": false,
"tax": 0,
"overridden_by_employee_id": null,
"overridden_date": null,
"active": true,
"total": 100,
"category_name": "Carpet",
"category_code": "CPT",
"name": "Staircase - Up to 16 steps"
},
{
"id": 3959187,
"invoice_id": 885632,
"package_id": null,
"service_id": 4171,
"product_id": null,
"coupon_id": null,
"quantity": 1,
"notes": "",
"price": 12,
"discount": 0,
"taxable": false,
"tax": 0,
"overridden_by_employee_id": null,
"overridden_date": null,
"active": true,
"total": 12,
"category_name": "Service Charge",
"category_code": "MISC",
"name": "Service Charge"
}
],
"address": {
"id": 965678,
"entity_type": 4,
"entity_id": 912099,
"template_id": 1,
"type_id": 1,
"template_group_id": null,
"address1": "15296 W Warren Ave",
"address2": null,
"unit_type": null,
"unit_number": "",
"county": null,
"city": "Denver",
"state": "CO",
"zipcode": "80228",
"country_id": 1,
"active": 1,
"latitude": null,
"longitude": null,
"unit_type_name": null,
"full_street": "15296 W Warren Ave",
"country_code": "US",
"type_name": null
},
"contact": {
"id": 912099,
"franchise_id": 2,
"type_id": 1,
"classification_id": 1,
"status_id": 2,
"campaign_id": null,
"title": null,
"first_name": "Anita ",
"middle_initial": null,
"last_name": "Ritchie",
"suffix": null,
"date_of_birth": null,
"gender": null,
"telephone": "8322283443",
"telephone2": null,
"telephone3": null,
"telephone4": null,
"email": "anitaritchie@icloud.com",
"url": null,
"account_number": null,
"company_name": null,
"date_created": "2017-10-03T12:00:03+00:00",
"last_modified": "2017-10-03T06:00:03+00:00",
"date_called": null,
"hide": false,
"active": true,
"unsubscribed": false,
"constant_contact_id": null,
"full_name": "Anita Ritchie"
},
"invoice_status": {
"id": 2,
"label": "Work Orders",
"label_singular": "Work Order",
"lookup_key": "work_order",
"date_update_key": "",
"date_key": "date_completed",
"display_order": 2,
"active": true
},
"is_completed": false
}
}
This endpoint retrieves a specific invoice by ID.
HTTP Request
GET http://crm.oxifresh.com/invoices/<ID>
Query Parameters
Parameter | Description |
---|---|
ID | The ID of the invoice to retrieve |
Response
Parameter | Description |
---|---|
invoice | The invoice data, including it's Contact, service Address, the list of Invoice Line Items, and Invoice Status |
Invoice Response
The invoice parameter should contain the following properties:
Parameter | Example | Description |
---|---|---|
franchise_id | 2 | The franchise ID this invoice belongs to (immutable) |
invoice_status_id | 2 | The type of invoice (Quote/Work Order/Invoice/Paid/Void) |
contact_id | 912099 | The ID of the Contact this invoice is for |
employee_id | 4151 | The ID of the Employee that is assigned to do this work |
work_address_id | 965678 | The ID of the Address the job is scheduled to take place |
campaign_id | 10896 | Deprecated - This will be removed soon, the same information is now available in the invoices_campaigns parameter |
location_id | 2 | The ID of the Location this job is located in. Can be changed until date_last_exported is set. |
sequence | 28374 | The auto-incremented number of the invoice, unique to the Franchise (ie. all franchises usually start at 1) |
po_number | null | A PO Number, not currently in use |
created_by_id | 2764 | The ID of the Employee that created this Invoice |
date_created | "2017-10-03T12:00:03+00:00" | The date it was created |
last_modified | "2017-10-03T06:00:03+00:00" | The date it was last modified, or it's Invoice Line Items were modified |
date_last_exported | null | Date it was last exported to a 3rd party vendor. If not null, Location ID becomes Immutable. |
date_due | "2017-10-03T12:00:03+00:00" | Date payment is due |
date_quoted | "2017-10-03T12:00:03+00:00" | Date the Quote (if applicable) was created |
date_completed | "2017-11-01T12:00:00+00:00" | Date the job was actually done (should syncronize with it's Appointment datetime) |
date_paid | null | Date the invoice was marked Paid |
notes | "Booked Online" | Any custom notes entered manually |
subtotal | 270 | Pre-tax subtotal |
tax_percent | 0.074999999999999997 | The tax rate |
tax_due | 0 | The effect amount of tax due (note that some line items may not be taxable, so this is not necessarily the subtotal times the tax_percent ) |
amount_due | 270 | The post-tax total amount, due in full |
amount_paid | 0 | The total of any payments made on this invoice |
original_subtotal | 0 | A record of the pre-tax subtotal when the job was originally booked as a Work Order (the final total may be different, the effective difference is the 'Upsell amount') |
quote_confirmed | false | A flag which when false indicates edits to subtotal should update original_subtotal |
imported | false | A flag to indicate the invoice was imported from a 3rd party source |
active | true | If false, the record is treated as if it were hard-deleted |
is_completed | true | Simplified flag to indicate if the invoice work has been marked as closed out and "completed" |
It will also list any of the following foreign-keyed associations:
Parameter | Description |
---|---|
address | The service Address for the job, includes the street address information & geocode |
contact | The Contact record, with their name, phone, email |
invoices_campaigns | An array of all the linked Invoice Campaigns, which in turn list the marketing Campaign |
invoice_line_items | A list of all the Invoice Line Items in the invoice |
Locations
Franchise Locations are geographical subgroups of zipcodes, which split a single franchise into multiple territories.
Each Location has a short code, a localized name, a set of metadata, and a list of zipcodes/postal codes which comprise the areas.
Get a list of locations
<?php
$curl = curl_init();
$data = [
'franchise_id' => 501
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/locations?'.$fieldsString);
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {franchsie_id: 501}
baseRequest({
url: "https://crm.oxifresh.com/api/locations",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"locations": [
{
"total_zipcodes": 19,
"id": 2060,
"code": "XX-0501A",
"label": "Location of Edgewater Carpet Cleaning",
"franchise_id": 501,
"is_default": true,
"phone": "303-555-1234",
"last_modified": "2016-10-06T19:22:16+00:00",
"active": true,
"full_name": "XX-0501A - Location of Edgewater Carpet Cleaning"
},
{
"total_zipcodes": 8,
"id": 2061,
"code": "XX-0501B",
"label": "Location of Lakewood Carpet Cleaning",
"franchise_id": 501,
"is_default": false,
"phone": "303-555-4444",
"last_modified": "2016-10-05T19:53:25+00:00",
"active": true,
"full_name": "XX-0501B - Location of Lakewood Carpet Cleaning"
}
],
"total": 2
}
This endpoint a paginated list of Locations.
HTTP Request
GET http://crm.oxifresh.com/locations
Query Parameters
Parameter | Default | Description |
---|---|---|
search | null | A generic search term that will return locations with matching names, phone numbers, or Codes |
showMetadata | false | Returns a list with all the detailed metadata for each location. |
For example, showMetadata will add the following locations_metadata node to each location:
{
"locations": [{
"total_zipcodes": 19,
"id": 2060,
"code": "XX-0501A",
"label": "Location of Edgewater Carpet Cleaning",
"franchise_id": 501,
"is_default": true,
"phone": "303-555-1234",
"last_modified": "2016-10-06T19:22:16+00:00",
"active": true,
"locations_metadata": {
"id": 1,
"location_id": 1,
"date_created": "2018-05-14T16:15:55+00:00",
"last_modified": "2018-07-31T08:00:12+00:00",
"address1": "143 Union St",
"address2": null,
"city": "Denver",
"state": "CO",
"zipcode": "80228",
"country_code": "US",
"territory_name_short": null,
"service_locations": "Edgewater, Denver, Wheat Ridge, Applewood, Lakewood",
"monday_hours_of_operation": "8:00-19:00",
"tuesday_hours_of_operation": "8:00-19:00",
"wednesday_hours_of_operation": "8:00-19:00",
"thursday_hours_of_operation": "8:00-19:00",
"friday_hours_of_operation": "8:00-19:00",
"saturday_hours_of_operation": "8:00-17:00",
"sunday_hours_of_operation": "",
"about_blurb": "A paragraph of text about the location...",
"in_the_community_blurb": "A paragraph about the local community ...",
"owner_bio_blurb": "A little bio about the owner(s) ...",
"owner_image_post_id": 1234,
"owner_name": "Rob White",
"google_verification_snippet": "",
"facebook_url": "https:\/\/www.facebook.com\/oxifresh\/reviews\/",
"google_url": "https:\/\/www.google.com\/search?q=Oxi+Fresh+of ...",
"mobile_google_url": "https:\/\/www.google.com\/search?q=Oxi+ ....",
"linkedin_url": null,
"twitter_url": null,
"yelp_url": "https:\/\/www.yelp.com\/biz\/ ...",
"instagram_url": null
},
"full_name": "XX-0501A - Location of Edgewater Carpet Cleaning"
}],
"total": 9,
"page": 1,
"limit": 1
}
Response
Parameter | Description |
---|---|
locations | A paginated list of locations, includes the relevant Location, Location Source, and if listed Contact and Invoice information. |
total | The total number of locations matching the input criteria |
Get a Specific Location
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/locations/4');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
baseRequest({
url: "https://crm.oxifresh.com/api/locations/4",
method: "GET"
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"location": {
"id": 2060,
"code": "XX-0501A",
"label": "Location of Edgewater Carpet Cleaning",
"franchise_id": 501,
"is_default": true,
"phone": "303-232-0120",
"last_modified": "2016-10-06T19:22:16+00:00",
"active": true,
"locations_metadata": {
"id": 1,
"location_id": 1,
"date_created": "2018-05-14T16:15:55+00:00",
"last_modified": "2018-07-31T08:00:12+00:00",
"address1": "143 Union St",
"address2": null,
"city": "Denver",
"state": "CO",
"zipcode": "80228",
"country_code": "US",
"territory_name_short": null,
"service_locations": "Edgewater, Denver, Wheat Ridge, Applewood, Lakewood",
"monday_hours_of_operation": "8:00-19:00",
"tuesday_hours_of_operation": "8:00-19:00",
"wednesday_hours_of_operation": "8:00-19:00",
"thursday_hours_of_operation": "8:00-19:00",
"friday_hours_of_operation": "8:00-19:00",
"saturday_hours_of_operation": "8:00-17:00",
"sunday_hours_of_operation": "",
"about_blurb": "A paragraph of text about the location...",
"in_the_community_blurb": "A paragraph about the local community ...",
"owner_bio_blurb": "A little bio about the owner(s) ...",
"owner_image_post_id": 1234,
"owner_name": "Rob White",
"google_verification_snippet": "",
"facebook_url": "https:\/\/www.facebook.com\/oxifresh\/reviews\/",
"google_url": "https:\/\/www.google.com\/search?q=Oxi+Fresh+of ...",
"mobile_google_url": "https:\/\/www.google.com\/search?q=Oxi+ ....",
"linkedin_url": null,
"twitter_url": null,
"yelp_url": "https:\/\/www.yelp.com\/biz\/ ...",
"instagram_url": null
},
"full_name": "XX-0501A - Location of Edgewater Carpet Cleaning"
}
}
This endpoint retrieves a specific location by ID, including it's detailed metadata by default. You can optionally specify a Code instead of an ID
HTTP Request
GET http://crm.oxifresh.com/locations/<ID>
Or:
GET http://crm.oxifresh.com/locations?code=<code>
Query Parameters
Parameter | Description |
---|---|
ID | The ID of the location to retrieve |
code | The Code of a location to retrieve |
Response
Parameter | Description |
---|---|
location | The location data including it's metadata |
Add A Location
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/locations');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$data = [
'code' => "XX-0501F",
'label' => 'Location of the Greater Area',
'franchise_id' => 501,
'is_default' => 1
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $fieldsString );
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {
'code' : "XX-0501F",
'label' : 'Location of the Greater Area',
'franchise_id' : 501,
'is_default' : 1
}
baseRequest({
url: "https://crm.oxifresh.com/api/locations",
method: "POST",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"location": {
"id": 2060,
"code": "XX-0501F",
"label": "Location of the Greater Area",
"franchise_id": 501,
"is_default": true,
"phone": "",
"last_modified": "2016-10-06T19:22:16+00:00",
"active": true,
"full_name": "XX-0501A - Location of the Greater Area"
}
}
This endpoint adds a new location to the system.
HTTP Request
POST http://crm.oxifresh.com/locations
Data Parameters
Parameter | Optional | Description |
---|---|---|
code | No | The short code name for the location |
label | No | The label for the location |
franchise_id | No | The ID of the Franchise for this location |
is_default | Yes | Indicates if this is the default location to use if a review or contact belongs to a franchise but doesn't match it's Location Zipcode's |
phone | Yes | An optional phone number to associate with this location |
Response
Parameter | Description |
---|---|
location | The new location data |
Delete a Location
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/locations/4');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
baseRequest({
url: "https://crm.oxifresh.com/api/locations/delete/4",
method: "DELETE"
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"location": {
"id": 2060,
"code": "XX-0501F",
"label": "Location of the Greater Area",
"franchise_id": 501,
"is_default": true,
"phone": "",
"last_modified": "2016-10-06T19:22:16+00:00",
"active": false,
"full_name": "XX-0501A - Location of the Greater Area"
}
}
This endpoint deletes a specific location by ID. Note that it's a "soft delete" - marking the active
flag false. This
removes it from GET
lists and aggregate locations.
HTTP Request
DELETE http://crm.oxifresh.com/locations/<ID>
Query Parameters
Parameter | Description |
---|---|
ID | The ID of the location to delete |
Response
Parameter | Description |
---|---|
location | The "deleted" location data |
List Location Zipcodes
<?php
$curl = curl_init();
$data = [
'franchise_id' => 501,
'code' => 'OF-0501A'
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/locationsZipcodes?'.$fieldsString);
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {
'franchise_id' : 501,
'code' : 'OF-0501A'
}
baseRequest({
url: "https://crm.oxifresh.com/api/locationsZipcodes",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"locationsZipcodes": [
{
"id": 1,
"location_id": 1,
"display_location_id": 2,
"zipcode": "80305",
"active": true,
"location": {
"id": 1,
"code": "OF-0501A",
"label": "Test A",
"franchise_id": 2,
"is_default": true,
"phone": null,
"last_modified": "2018-05-16T17:27:50+00:00",
"external_id": null,
"active": true,
"locations_metadata": null,
"full_name": "OF-0501A - Test A"
},
"display_location": {
"id": 2,
"code": "OF-0501B",
"label": "Test B",
"franchise_id": 2,
"is_default": false,
"phone": null,
"last_modified": "2018-05-16T17:28:02+00:00",
"external_id": null,
"active": true,
"locations_metadata": null,
"full_name": "OF-0002B - Test B"
}
},
{
"id": 2,
"location_id": 1,
"display_location_id": null,
"zipcode": "80301",
"active": true,
"location": {
"id": 1,
"code": "OF-0501A",
"label": "Test A",
"franchise_id": 2,
"is_default": true,
"phone": null,
"last_modified": "2018-05-16T17:27:50+00:00",
"external_id": null,
"active": true,
"locations_metadata": null,
"full_name": "OF-0501A - Test A"
},
"display_location": null
}
]
}
This endpoint a paginated list of Locations Zipcodes. You can search for a Location by it's ID explicitly, ex:
https://crm.oxifresh.com/locationsZipcodes/1
.. or you can search for one by it's Code (ex. https://crm.oxifresh.com/locationsZipcodes/OF-0501A
). This will return partial matches, so you should include franchise_id with the search to eliminate most partial match issues.
HTTP Request
GET http://crm.oxifresh.com/locationsZipcodes
Query Parameters
Parameter | Default | Description |
---|---|---|
code | null | Returns all Location Zipcodes which match the specified Location code |
zipcode | null | Returns a single Location Zipcode that matches the zipcode provided |
Response
Parameter | Description |
---|---|
locationsZipcodes | A paginated list of locations zipcodes, with their nested Location entity. Some may also list a "display location" - which should be used for any public-facing listings for a zipcode, as they may technically be listed internally under one Location but externally to customers are sorted by this "display" location |
Postal Codes
Allows various lookups for Postal Code/Zipcode listings, such as Owned and Zones lists.
Get Zone by Zipcode
<?php
$curl = curl_init();
$data = [
'franchise_id' => 501
];
// For Countries with prefixed Postal Codes (CA), you can also list the n-digit prefix, eg. "V6C"
$postalCode = "00000";
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_URL, "https://crm.oxifresh.com/postalCodes/getZoneForPostalCode/$postalCode?$fieldsString");
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {
'franchise_id' : 501
}
// For Countries with prefixed Postal Codes (CA), you can also list the n-digit prefix, eg. "V6C"
var postalCode = '00000';
baseRequest({
url: "https://crm.oxifresh.com/api/postalCodes/getZoneForPostalCode/" + postalCode + "",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"zone": {
"id": 58653,
"zip_group_id": 2,
"franchise_id": 501,
"name": "Zone 1",
"color": "FF99CC",
"default_group": false,
"_matchingData": {
"ZipGroupsZipcodes": {
"id": 36950,
"zip_group_id": 2,
"zip_group_sub_group_id": 58653,
"franchise_id": 501,
"zipcode": "00000"
}
}
}
}
This endpoint gets a Zone for a specific Address ID. Note that you must supply the Franchise ID as multiple Franchises may all list this Zipcode in their Zones (they do not have to be Owned by them to service that area).
HTTP Request
GET http://crm.oxifresh.com/postalCodes/getZoneForPostalCode/<postal_code>
Query Parameters
No extra query parameters
Response
Parameter | Description |
---|---|
zone | A Zip Group Sub Group listed under the Zone group, which contains the Postal Code |
Get Zone By Address ID
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, "https://crm.oxifresh.com/postalCodes/getZoneByAddressId/11");
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
baseRequest({
url: "https://crm.oxifresh.com/api/postalCodes/getZoneByAddressId/11",
method: "GET"
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"zone": {
"id": 1642,
"zip_group_id": 2,
"franchise_id": 501,
"name": "Zone 2",
"color": "339966",
"url": "",
"location_label": null,
"default_group": null,
"_matchingData": {
"ZipGroupsZipcodes": {
"id": 1579606,
"zip_group_id": 2,
"zip_group_sub_group_id": 1642,
"franchise_id": 501,
"zipcode": "V1X"
}
}
},
"address": {
"id": 680448,
"entity_type": 4,
"entity_id": 658019,
"template_id": 1,
"type_id": 1,
"template_group_id": 0,
"address1": "1725 Fake St.",
"address2": "",
"unit_type": 1,
"unit_number": "3",
"county": "",
"city": "Kelowna",
"state": "BC",
"zipcode": "V1X7H1",
"country_id": 2,
"active": 1,
"geocode": "49.8864164,-119.4211188",
"subset": "",
"country": {
"id": 2,
"code": "CA",
"name": "CANADA",
"allow_postal_code_wildcards": true,
"minimum_postal_code_digits": 3,
"active": 1
}
}
}
This endpoint gets a Zone for a specific Address ID. Note that the Franchise account will be automatically determined by the linked Entity specified in the Addresss's entity_type/entity_id pair.
Your API user must have access to that Franchise account to retrieve the Zone.
HTTP Request
GET http://crm.oxifresh.com/postalCodes/getZoneForPostalCode/<postal_code>
Query Parameters
No extra query parameters
Response
Parameter | Description |
---|---|
zone | A Zip Group Sub Group listed under the Zone group, which contains the Postal Code |
address | The matching Address's information, along with nested Country information |
Reports
Our newest reports system offers API-compatible data formats that can be used for more than just polling for data to show to users.
Several reports actually assist in aggregating data in near-real-time into pre-computed data sets, aggregating job data in practical, cross-franchise data sets that vastly simplify tasks like querying for lists of unique marketable emails or addresses.
Marketing
Households
<?php
$curl = curl_init();
$data = [
'franchise_id' => 501,
'last_modified.after' => "2019-02-27 18:19:00"
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/marketingLists/households?'.$fieldsString);
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {franchsie_id: 501, "last_modified.after": "2019-02-27 18:19:00"}
baseRequest({
url: "https://192.168.50.4/reports/marketingLists/households",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"households": [
{
"_id": {
"$oid": "5c781c4707612912a535b752"
},
"address1": "300 Man Dr.",
"address2": "",
"unit_number": "300",
"county": null,
"city": "Boulder",
"state": "CO",
"zipcode": "80303",
"country_id": 1,
"active": true,
"date_created": "2019-02-28T17:37:11+00:00",
"most_recent": {
"franchise": {
"id": 501
},
"invoice": {
"id": 123456,
"franchise_id": 2,
"date_completed": "2019\/02\/28 09:00:00"
},
"contact": {
"id": 789456,
"franchise_id": 2,
"email": "sample@oxifresh.com"
}
},
"last_modified": "2019-02-28T18:34:20+00:00"
}
],
"total": 1,
"limit": 100,
"page": 1,
"match": {
"last_modified": {
"$gte": {
"$date": {
"$numberLong": "1551287940000"
}
}
}
}
}
This report returns a list of unique addresses and the most recent job & contact data for each. It can be optionally
limited to specific franchises, a specific service window (with most_recent.invoice.date_completed.after
and
most_recent.invoice.date_completed.before
), or just the most recent changes since a specified date (with
last_modified.after
)
This is well suited to pulling lists of marketable addresses for "Last Serviced n-months ago" type mailing campaigns.
HTTP Request
GET http://crm.oxifresh.com/locations
Query Parameters
Parameter | Default | Description |
---|---|---|
last_modified.after | null | Specify a date to only retrieve household records updated after that date |
most_recent.invoice.date_completed.after | null | Specify a date to only retrieve records with jobs completed after that date |
most_recent.invoice.date_completed.before | null | Specify a date to only retrieve records with jobs completed before that date |
Response
Parameter | Description |
---|---|
households | A paginated list of unique addresses, which includes all the available most recent basic Franchise, Invoice, and Contact information |
match | The parsed criteria used in the search |
total | The total number of unique addresses matching the input criteria |
Emails
<?php
$curl = curl_init();
$data = [
'franchise_id' => 501,
'most_recent.invoice.date_completed.after' => "2018-10-01 00:00:00",
'most_recent.invoice.date_completed.before' => "2018-11-01 00:00:00"
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/emails?'.$fieldsString);
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {
franchsie_id: 501,
"most_recent.invoice.date_completed.after": "2018-10-01 00:00:00",
"most_recent.invoice.date_completed.before": "2018-11-01 00:00:00",
}
baseRequest({
url: "https://192.168.50.4/reports/marketingLists/emails",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"marketingEmails": [
{
"_id": {
"$oid": "5c7829a4076129180b322dc2"
},
"email": "ahoffner@gmail.com",
"is_unsubscribed": false,
"unsubscribed_on_date": null,
"most_recent": {
"franchise": {
"id": 501
},
"invoice": {
"id": 773786,
"franchise_id": 2,
"date_completed": "2019\/02\/28 09:00:00"
},
"address": {
"address1": "300 Man Dr.",
"address2": "",
"unit_number": "",
"county": null,
"city": "Boulder",
"state": "CO",
"zipcode": "80303",
"country_id": 1
}
},
"date_created": "2019-02-28T18:34:12+00:00",
"last_modified": "2019-02-28T18:34:18+00:00"
}
],
"total": 1,
"limit": 100,
"page": 1,
"match": {
"last_modified": {
"$gte": {
"$date": {
"$numberLong": "1551287940000"
}
}
}
}
}
This report is generally identical to Households, but it instead returns a list of unique emails and the most recent job & contact data for each. It can also be filtered similarly, searchable by franchise ID, last job date, the date last modified.
It is similarly well suited to pulling lists of marketable emails for "Last Serviced n-months ago" type email campaigns.
HTTP Request
GET http://crm.oxifresh.com/locations
Query Parameters
Parameter | Default | Description |
---|---|---|
last_modified.after | null | Specify a date to only retrieve household records updated after that date |
most_recent.invoice.date_completed.after | null | Specify a date to only retrieve records with jobs completed after that date |
most_recent.invoice.date_completed.before | null | Specify a date to only retrieve records with jobs completed before that date |
Response
Parameter | Description |
---|---|
marketing_emails | A paginated list of unique addresses, which includes all the available most recent basic Franchise, Invoice, and Contact information |
match | The parsed criteria used in the search |
total | The total number of unique addresses matching the input criteria |
Reviews
Get a list of reviews
<?php
$curl = curl_init();
$data = [
'location' => [
'code' => '2A'
]
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/reviews?'.$fieldsString);
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
foreach($response['reviews'] as $review){
echo "<p>$review->comment<br>(Rated $review->rating / 10)</p>";
}
var data = {
'location' : {
'code' : '2A'
}
}
baseRequest({
url: "https://crm.oxifresh.com/api/reviews",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"reviews": [
{
"id": 1,
"franchise_id": 501,
"reviews_source_id": 1,
"comment": "This was great",
"rating": 9,
"allowed_public": 1,
"censored": 0,
"zipcode": "80305",
"date_reviewed": "2016-09-21T15:52:35+0000",
"location_id": 4,
"invoice_id": 146745,
"contact_id": 230143,
"date_created": "2016-09-22T16:54:23+0000",
"last_modified": "2016-09-27T20:56:20+0000",
"active": true,
"invoice": {
"id": 146745,
"franchise_id": 501,
"invoice_status_id": 4,
"contact_id": 230143,
"employee_id": 1730,
"work_address_id": 228878,
"campaign_id": null,
"sequence": 6998,
"po_number": null,
"created_by_id": 2050,
"date_created": "2011-09-21T09:13:11+00:00",
"last_modified": "2015-02-27T04:06:21+00:00",
"date_due": "2011-09-26T00:00:00+00:00",
"date_quoted": "2011-09-26T00:00:00+00:00",
"date_completed": "2011-09-26T00:00:00+00:00",
"date_paid": "2011-09-30T00:00:00+00:00",
"notes": "",
"subtotal": 131,
"tax_percent": 0.087,
"tax_due": 0,
"amount_due": 131,
"amount_paid": 131,
"original_subtotal": 146,
"quote_confirmed": true,
"imported": null,
"active": true,
"address": {
"id": 228878,
"entity_type": 4,
"entity_id": 230143,
"template_id": 1,
"type_id": 1,
"template_group_id": null,
"address1": "123 Real St.",
"address2": null,
"unit_type": 1,
"unit_number": null,
"county": null,
"city": "WESTMINSTER",
"state": "CO",
"zipcode": "80031",
"country_id": 1,
"active": 1,
"geocode": "39.8981100,-105.0344940",
"subset": null
},
"summary": null
},
"location": {
"id": 1,
"code": "OF-0002A",
"label": "Oxi Fresh of Location Area",
"franchise_id": 2,
"is_default": false,
"phone": "303-555-0120",
"last_modified": "2016-10-10T22:28:56+00:00",
"active": true,
"full_name": "OF-0002A - Oxi Fresh of Location Area"
},
"contact": {
"id": 230143,
"franchise_id": 501,
"type_id": 1,
"classification_id": 1,
"status_id": 2,
"campaign_id": 0,
"title": "",
"first_name": "Real",
"middle_initial": "",
"last_name": "Customername",
"suffix": "",
"date_of_birth": null,
"gender": "",
"telephone": "3035551235",
"telephone2": "",
"telephone3": "",
"telephone4": "",
"email": "realaddress@oxifresh.com",
"url": "",
"account_number": "",
"company_name": "",
"date_created": "2011-09-21T00:00:00+00:00",
"last_modified": "2013-09-05T20:11:12+00:00",
"date_called": null,
"hide": false,
"active": false,
"unsubscribed": false,
"constant_contact_id": null
},
"reviews_source": {
"id": 2,
"lookup_key": "listen_360",
"name": "Listen360",
"is_default": false,
"active": true
}
},
{
"id": 2,
"franchise_id": 501,
"reviews_source_id": 2,
"comment": "This was not as great",
"rating": 2,
"allowed_public": 0,
"censored": 1,
"zipcode": "80129",
"date_reviewed": "2016-09-10T15:52:35+0000",
"location_id": 4,
"invoice_id": null,
"contact_id": null,
"date_created": "2016-09-22T16:54:23+0000",
"last_modified": "2016-09-27T20:56:20+0000",
"active": true,
"location": {
"id": 2,
"code": "2A",
"label": "Location Of West Coast",
"franchise_id": 501,
"is_default": true,
"phone": "30354551234",
"active": true,
"full_name": "2B - Location Of West Coast"
},
"contact": null,
"reviews_source": {
"id": 2,
"lookup_key": "listen360",
"name": "Listen360",
"is_default": false,
"active": true
}
}
]
}
This endpoint gets a paginated list of customer Reviews. Note that reviews are aggregated from multiple different sources.
HTTP Request
GET http://crm.oxifresh.com/reviews
Query Parameters
Parameter | Default | Description |
---|---|---|
location | null | An array of location information to limit review results with. Examples include: Code (ex. 2B ), Label (ex. Location of Denver ) |
search | null | A generic search term that will return reviews with matching comments, ratings, review sources, and contact names |
showNotAllowedPublic | false | Set to true to include reviews that were not indicated to be shown publicly |
showCensored | false | Set to true to include reviews that have been "censored" for various reasons (profanity, trolling, etc.) |
Response
Parameter | Description |
---|---|
reviews | A paginated list of reviews, includes the relevant Location, Review Source, and if listed Contact and Invoice information. |
total | The total number of reviews matching the input criteria |
Get a Specific Review
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/reviews/4');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
baseRequest({
url: "https://crm.oxifresh.com/api/review/4",
method: "GET"
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"review": [
{
"id": 1,
"franchise_id": 2,
"reviews_source_id": 1,
"comment": "This was great",
"rating": 9,
"allowed_public": 1,
"censored": 0,
"zipcode": "80305",
"date_reviewed": "2016-09-21T15:52:35+0000",
"location_id": 4,
"invoice_id": null,
"contact_id": null,
"date_created": "2016-09-22T16:54:23+0000",
"last_modified": "2016-09-27T20:56:20+0000",
"active": true,
"location": {
"id": 4,
"code": "2D",
"label": "Location Of Oz",
"franchise_id": 2,
"is_default": false,
"phone": null,
"active": true,
"full_name": "2D - Location Of Oz"
},
"contact": null,
"reviews_source": {
"id": 1,
"lookup_key": "website",
"name": "Oxifresh.com",
"is_default": true,
"active": true
}
}
]
}
This endpoint retrieves a specific review by ID.
HTTP Request
GET http://crm.oxifresh.com/reviews/<ID>
Query Parameters
Parameter | Description |
---|---|
ID | The ID of the review to retrieve |
Response
Parameter | Description |
---|---|
review | The review data |
Add A Review
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/reviews');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$data = [
'rating' => 9,
'comments' => 'Service was great.',
'contacts' => [
'first_name' => 'Kaiden',
'last_name' => 'Alenko',
],
'addresses' => [
'zipcode' => 80305
]
];
$fieldsString = http_build_query($data);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $fieldsString );
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
var data = {
'rating' : 9,
'comments' : 'Service was great.',
'contacts' : {
'first_name' : 'Kaiden',
'last_name' : 'Alenko',
},
'addresses' : {
'zipcode' : 80305
}
}
baseRequest({
url: "https://crm.oxifresh.com/api/reviews",
method: "POST",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"review": {
"id": 15,
"franchise_id": 2,
"reviews_source_id": 1,
"comment": null,
"rating": 8,
"allowed_public": 1,
"censored": 0,
"zipcode": "80111",
"date_reviewed": "2016-09-28T19:34:47+0000",
"location_id": 2,
"invoice_id": null,
"contact_id": 9163,
"date_created": "2016-09-28T19:34:47+0000",
"last_modified": "2016-09-28T19:34:47+0000",
"active": true,
"invoice": null,
"location": {
"id": 2,
"code": "2B",
"label": "Location Of Whereever",
"franchise_id": 2,
"is_default": false,
"phone": null,
"active": true,
"full_name": "2B - Location Of Whereever"
},
"contact": {
"id": 9163,
"franchise_id": 2,
"type_id": 1,
"classification_id": 1,
"status_id": 2,
"campaign_id": 0,
"title": null,
"first_name": "Andy",
"middle_initial": null,
"last_name": "Horner",
"suffix": null,
"date_of_birth": null,
"gender": "U",
"telephone": "3037791389",
"telephone2": "3039467226",
"telephone3": "",
"telephone4": null,
"email": null,
"url": null,
"account_number": null,
"company_name": "Andy Horner",
"date_created": "2009-09-09T00:00:00+0000",
"last_modified": "2010-02-26T04:23:53+0000",
"date_called": "2009-09-09T00:00:00+0000",
"hide": false,
"active": true,
"unsubscribed": false,
"constant_contact_id": null,
"full_name": "Andy Horner"
},
"reviews_source": {
"id": 1,
"lookup_key": "website",
"name": "Oxifresh.com",
"is_default": true,
"active": true
}
}
}
This endpoint adds a new review to the system.
Note it is recommended you supply at least:
- The Contact's First & Last Name
- The zipcode of the service location
If you supply these data points along with the Review, we can sort the review into the proper Location (ex. 2A - Oxi Fresh of Denver, and implicitly the proper Franchise #2) and also attempt to match the review with a specific Contact in our system.
HTTP Request
POST http://crm.oxifresh.com/reviews
Data Parameters
Parameter | Optional | Default | Description |
---|---|---|---|
rating* | No | null | A numeric rating from 0 - 10 (worst to best, respectively) |
comment* | Yes | null | The comment text |
contacts* | Yes | null | Can contain an array of all known Contact information (first_name, last_name, email, telephone, for example) which will be used to make a best-guess match for who the review is in our database |
addresses* | Yes | null | Can contain an array of all known Address information (zipcode, address_1, city, state, etc.) which will be used both to help refine the best-guess match for the Contact, and the zipcode is also used to sort into the proper Location automatically. |
allowed_public | Yes | true | Set to false to indicate the reviewer did not want the review made public |
censored | Yes | false | Set to true to indicate in advance if the review should be "censored" (note it may be marked censored automatically based on comments contents). |
date_reviewed | Yes | Now() | Defaults to the current time, but optionally can specify a different date/time string (YYYY-MM-DD HH:ii:ss) if the review was provided at a different point in time. |
review_source_id | Yes | Varies | The ReviewsSource ID, defaults to a configured source, usually "website". Query GET \reviewsSources to view a list of possible sources. |
contact_id | Yes | null | If known, you can supply the exact Contact ID for the review |
invoice_id | Yes | null | If known, you can supply the exact Invoice ID for the review |
location_id | Yes | null | If known, you can supply the exact Location ID for the review |
*recommended fields
Response
Parameter | Description |
---|---|
review | The new review data, including any matched extended information (Contact, Invoice, Location, ReviewsSource |
Delete a Review
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/reviews/4');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
baseRequest({
url: "https://crm.oxifresh.com/api/reviews/4",
method: "DELETE"
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"review": [
{
"id": 1,
"franchise_id": 2,
"reviews_source_id": 1,
"comment": "This was great",
"rating": 9,
"allowed_public": 1,
"censored": 0,
"zipcode": "80305",
"date_reviewed": "2016-09-21T15:52:35+0000",
"location_id": 4,
"invoice_id": null,
"contact_id": null,
"date_created": "2016-09-22T16:54:23+0000",
"last_modified": "2016-09-27T20:56:20+0000",
"active": false
}
]
}
This endpoint deletes a specific review by ID. Note that it's a "soft delete" - marking the active
flag false. This
removes it from GET
lists and aggregate reviews.
HTTP Request
DELETE http://crm.oxifresh.com/reviews/<ID>
Query Parameters
Parameter | Description |
---|---|
ID | The ID of the review to delete |
Response
Parameter | Description |
---|---|
review | The "deleted" review data |
Get Review Averages
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://crm.oxifresh.com/reviews/average/2B');
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
curl_close($curl);
echo $response;
baseRequest({
url: "https://crm.oxifresh.com/api/reviews/average/2B",
method: "GET"
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"average_rating": 8.7,
"total": 72
}
This endpoint returns the average rating and total number of ratings, either overall or for the specified Location Code.
HTTP Request
GET http://crm.oxifresh.com/reviews/average/<location_code>
Query Parameters
Parameter | Description |
---|---|
location_code | Optionally a Location code (ex. 2B ) to restrict results to. |
Response
Parameter | Description |
---|---|
average_rating | The average rating down to one decimal point |
total | The total number of matching ratings |
retrieves a specific review by ID.
Tax Rates
Enables lookup of tax rates for a given postal code, with up to rooftop-level accuracy when given the full address.
Get Zone by postal code
<?php
$curl = curl_init();
$postalCode = '80228';
curl_setopt($curl, CURLOPT_URL, "https://crm.oxifresh.com/taxRates/$postalCode");
curl_setopt($curl, CURLOPT_USERPWD, 'api_username:api_password');
$response = json_decode(curl_exec($curl), true);
$info = curl_getinfo($curl);
curl_close($curl);
if($info['http_code'] != '200') {
throw new Exception('HTTP Request Exception: '.print_r($info,1));
}
// For Countries with prefixed Postal Codes (CA), you can also list the n-digit prefix, eg. "V6C"
var postalCode = '80228';
baseRequest({
url: "https://crm.oxifresh.com/taxRates/" + postalCode + "",
method: "GET",
body: data
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
// Success!
}
}
)
This will return JSON results structured like this:
{
"taxRate": {
"rate": "0.045",
"postal_code": "80228",
"city": "LAKEWOOD",
"state": "CO",
"county": "JEFFERSON",
"expiration_date": "2019-09-05T16:42:43-06:00"
}
}
This endpoint returns the best-match tax rate for the postal code provided. It returns the sales tax rate, what city/state/county matched for the postal code & other address information provided, and also the date that rate will expire from caches (typically a set number of days since last querying for the rate, with a hard stop at the end of each year).
Note that most rates are determined by a 3rd party company which specializes in taxes, but each postal code & each franchisee may override their recommendations on a case-by-case basis. This endpoint will return the overridden rates if on-file agnostically. As the output JSON looks identical, you don't need to handle overriden rates differently.
HTTP Request
GET http://crm.oxifresh.com/taxRates/<postal_code>
Query Parameters
All additional query parameters listed here are optional, but can improve accuracy.
Parameter | Description |
---|---|
city | The city for the sale(RECOMMENDED - vastly improves accuracy, in the US particularly) |
state | The state for the sale (Use 'state' even for Countries that label these differently, like Canada's 'Provinces') |
county | The county for the sale |
address1 | The street address for the sale |
address2 | The 2nd line address (Apt or unit #) for the sale |
Response
Parameter | Description |
---|---|
rate | The sales tax rate for the location. Note that only services flagged as "taxable" should apply this rate in sales |
postal_code | The postal code specified, for the rate returned |
city | The city for the rate returned. May not match input City if it was deemed invalid by the tax rate provider internally (it will return a best-guess city for the postal codeinstead) |
state | The State for the rate returned. Like City, may not match input State if it was deemed invalid |
county | The County for the rate returned. Like City, may not match input County if it was deemed invalid |
Push API
In addition to our normal API endpoints, in which you can request data on-demand, we also have a Push API that allows you to subscribe to one or more changes to certain entities in near-real-time.
Supported Entities
We currently can forward changes to the following:
- Appointments
- Bills (Franchisee Royalties & Fees)
- Campaigns
- Contacts
- Coupon Packs
- Coupons
- Employees
- Franchises
- Invoices
- Locations & Location Zipcodes
- Services Settings
- To-Do's
- Zip Groups & Zip Group Zipcodes (used for Scheduling)
Setup
The simplest way is to create a new subscription in the Push API section of the Scheduling Center, found under the Other umbrella: http://crm.oxifresh.com/pushApiEndpoints
Creating A New Endpoint
When creating a new endpoint, you supply the URL data will be POST'ed to. Optionally you can include an API Key in the URL itself.
We can forward data in the same JSON format used in regular API responses, or optionally in a Key-Value format used by browsers when submitting forms.
Select what entity this endpoint will monitor, and if you wan to receive all change (Adds, Edits, Deletes) - or only a specific action.
Release Notes
Below are any relevant changes to the Scheduling Center API, including migration guides for breaking changes.
v1.6.1 Hotfix for Tax Rates API
Released on 8/4/2019
This release primarily added a new Tax Rates API & also added City to the New Customer workflow to improve tax rate accuracy overall.
New Features
- More accurate Tax Rates API powered by TaxJar
- Enabled IP-based API Rate Limit Throttling to establish sane limits to requests per hour. Special X-headers display rate limit counts for reference
v1.6 Short Stack
Released on 3/7/2019
This release added support for Multi-Franchise Owners and Techs - a feature that allows an Employee to belong to more than one Franchise at a time.
It also will begin to (softly) enforce emails uniqueness on every Employee account. Users will be prompted to verify ownership of their emails if they have not already done so, and also to change their email (or request it removed from other accounts) if it's listed on multiple accounts.
It also contains background tools that better support SSO in general, including our Naranga integration, and updates Listen360's SSO integration to bring it inline with newer conventions.
New Features
- All main API calls now support basic field & association filtering. See the Filtering Fields section of the docs for more info.
- Push API now in open beta. This feature has been proved out internally & is ready for general use. Users can now subscribe to receive all changes made to most major Entity types in our system in real time. See the Push API docs for more info.
- Advanced SSO integration system - this is a suite of background processors that keep data in sync. It contains detailed request logs and propagation information that can be referenced to troubleshoot sync issues and verify functionality.
Migration
Most of the changes are backwards compatible, however there are some notable changes to Employees & to deactivated entities in general:
Changes for Employees Franchises
Employees no longer have a single Franchise ID field listed on the entity. The new potential one-to-many relationship has necessitated the addition of a new EmployeesFranchises
table that stores a list of all the Franchises each Employee has access to.
To assist with migration, the existing Franchise ID field was left on each employee for now, but it has been renamed to deprecated_franchise_id
.
Stricter Active Flag Conventions
Some improvements and bug fixes to the central pieces that filter out soft-deleted entities (by marking their active
flags false) will more strictly filter out these de-activated records.
If your API has been coded to rely on inactive entities being errantly included in index lists or in GET
endpoints, you may need to make modifications. All documented index
lists now support
the showInactive
that will include deactivated results in the listings (the legacy named showHidden
is still supported for now as well, until v1.7).
v1.5 Short Stack
Released on 12/19/2018
This release primarily included a suite of new background processing systems that could synchronize data changes (like Employees and Franchises) from the Scheduling Center to relevant 3rd parties.
This automated provisioning and updates system was required for a number of Single Sign Ons to work seamlessly, or improved functionality for some existing ones.
It also enabled our Push API, which can forward changes to a number of entities in near-real-time to interested parties.
It also contains alpha-versions of two upcoming features, Households & Marketing Emails, two systems which compile job data in near-real-time into rows of unique addresses and emails (respectively) listing the most recent service dates, average review scores, and most recent invoice IDs. These system will go live in the next release, v1.6.
Migration
Few major changes were made to existing features nor were any fields removed. Likely your integration will remain largely backwards compatible.
You may need to check Location status
which will indicate if it's ready to be published or still in draft
state, indicating its still under construction internally.
Entity Changes
A couple of tables added creation & modification fields: - Employees - SingleSignOnsIds
Added |
---|
date_created |
created_by_employee_id |
last_modified |
last_modified_by_employee_id |
- Locations
Added | Desc |
---|---|
status | A string who can be draft , published , or hidden . Location will not be propagated to 3rd parties if it's still in draft status |
- Locations Metadata
Added | Desc |
---|---|
geo_modifier | A string representation of the address location (ex. "South Denver") |
- Modules
name
maximum field length was increased to 150
v1.4.7
This release standardized several field names in Todos and Billing, as well as cleaned up some deprecated fields in Franchises.
Removed Fields
- Franchises
Removed |
---|
address1 |
address2 |
city |
state |
zipcode |
fax |
short_emails |
long_emails |
Addresses have internally already been tracked using the address_id
association, this removes them from API output to
avoid confusion.
The two email fields, short_emails
and long_emails
were also deprecated internally for some time. To get lists of
a franchise's Owner's emails, query the Employees API endpoint for those in the "Franchise Owners" Group.
Renamed Fields
- Todos
Old Field Name | New Name |
---|---|
created_by_id | created_by_employee_id |
last_modified_by_id | last_modified_by_employee_id |
Note these are now tracked automatically and cannot be set manually. 'data_exports_file_id'
To-Be-Documented
The following Controllers generally support the default actions (GET
, POST
, PUT
, PATCH
, DELETE
),
but no additional documentation has been written for them at this time.
Note that there may be endpoints accessible in addition to those listed here, however they usually are from Legacy packages which rarely follow the conventions of our newer modules and require additional domain knowledge to use.
Campaigns Sub-Categories
Concerns the top-level Categories marketing campaigns are sorted into.
HTTP Request
http://crm.oxifresh.com/campaignsSubcategories
Coupons
A coupon is a (usually complex) discount set which contains rules for applicability & discount rates depending on how the rules are satisfied.
HTTP Request
http://crm.oxifresh.com/coupons
Coupons Packs
A group of re-usable Coupons. Can be listed on any number of marketing Campaigns.
HTTP Request
http://crm.oxifresh.com/couponsPacks
Coupons Rules
A single discount rate & a potentially a list of Coupons Rules Items to qualify for that rate. A single Coupon may list multiple Coupons Rules to accomplish complex discount situations.
HTTP Request
http://crm.oxifresh.com/couponsRules
Data Exports
A record of a a data export job & it's status.
HTTP Request
http://crm.oxifresh.com/dataExports
Data Exports Files
The actual completed files exported out (usually CSV or XML files)
HTTP Request
http://crm.oxifresh.com/dataExportsFiles
Data Exports Formats
Predefined, re-usable formats to export data in once or routinely.
HTTP Request
http://crm.oxifresh.com/dataExportsFormats
Data Exports Formats Fields
The individual data fields, constants, or special calculations to include in a given Data Export Format
HTTP Request
http://crm.oxifresh.com/dataExportsFormatsFields
Data Exports Logs
Entity-level records of an export activity for auditing purposes.
HTTP Request
http://crm.oxifresh.com/dataExportsLogs
Data Exports Logs
Entity-level records of an export activity for auditing purposes.
HTTP Request
http://crm.oxifresh.com/dataExportsLogs
Data Exports Uploaders
Locations / information on where to send exported data when scheduled
HTTP Request
http://crm.oxifresh.com/dataExportsUploaders
Data Exports Uploads Schedules
Scheduled uploads of pre-defined Export Formats to predefined Uploaders
HTTP Request
http://crm.oxifresh.com/dataExportsUploaders
Employees
Concerns the Employees (users) that can login into the system, and information about the user.
HTTP Request
http://crm.oxifresh.com/employees
Equipment
Concerns the Equipment that can be assigned to Employees on the Schedule, which determines what types of Service's Categories they can perform on what days.
HTTP Request
http://crm.oxifresh.com/equipment
Franchises
Lists the Franchises accounts. Supports only GET
, POST /activate
, and DELETE /deactivate
.
HTTP Request
http://crm.oxifresh.com/campaignsSubcategories
Groups
Concerns the Employee Groups, as well as the set of Permissions listed in each Group.
HTTP Request
http://crm.oxifresh.com/groups
Help Information
A single record containing a phone, email, and Usersnap API key for users to get help with.
HTTP Request
http://crm.oxifresh.com/helpInformation
Landing / What's New
Can provide HTML from the GET
action - containing a page describing the latest new features in the
SchedulingCenter.
HTTP Request
http://crm.oxifresh.com/landing
http://crm.oxifresh.com/whatsNew
Line Items
Can return a list of an invoices line items with GET
, or a list of services/products that are able to be added to and invoice with GET /list
HTTP Request
http://crm.oxifresh.com/lineItems
Locations Zipcodes
Concerns the Zipcodes assigned to Locations. Zipcodes-Locations are one-to-one.
Also implements:
* exists
- to check for existing zipcode-location assignments
* reassign
- to move a Zipcode to a different location_id
* saveList
- to save a bulk comma-separated list of zipcodes
to a location_id
HTTP Request
http://crm.oxifresh.com/locationsZipcodes
Message Of The Day
Concerns the banner-style messages visible at the top of the page.
HTTP Request
http://crm.oxifresh.com/messageOfTheDay
Message Of The Day Logs
Concerns the log of who has viewed each of Message of the Day messages.
HTTP Request
http://crm.oxifresh.com/messageOfTheDayLogs
Online Scheduling Services
Concerns the list of services available via Online Scheduling.
HTTP Request
http://crm.oxifresh.com/onlineSchedulingServices
Online Scheduling Service Categories
Concernss the categories the Online Scheduling Services are grouped under.
HTTP Request
http://crm.oxifresh.com/onlineSchedulingServicesCategories
Popups
Concerns the popup-style messages attached to Contacts visible when pulling up their information in the Calendar
HTTP Request
http://crm.oxifresh.com/popups
Popups Logs
Concerns the log of who viewed each of the popup-style messages.
HTTP Request
http://crm.oxifresh.com/popupsViewLog
Punch Clock
Concerns the punch clock times used to record when Employees have started / ended working. Includes a
punchTheClock
method which toggles the punched in / punched out status of the logged in user (you)
HTTP Request
http://crm.oxifresh.com/punchClock
Releases
Concerns the information about the Scheduling Center's software releases, for informational purposes.
HTTP Request
http://crm.oxifresh.com/releases
Scheduling
Concerns the Employee's Schedule (times available) and Assignments (what Equipment or service Categories they can work).
Does not support JSON requests at this time, and only the GET
and calendar
actions.
HTTP Request
http://crm.oxifresh.com/scheduling
Scheduling Modules
Concerns the list of "modules" that can impact an Employee's scheduling (ex. Franchise Hours, Employee Hours, Zone Assignments, etc.)
HTTP Request
http://crm.oxifresh.com/schedulingModules
Scheduling Module Preferences
Concerns which of the "optional" Scheduling Modules each Franchisee has elected to use (ex. Category Assignments instead
of Equipment Assignments). Only supports PATCH
and DELETE
.
HTTP Request
http://crm.oxifresh.com/schedulingModulesPreferences
Scheduling Times - Custom Dates
Concerns the one-off, specific date entries for time-based Scheduling modules (ex. Franchise & Employee hours)
HTTP Request
http://crm.oxifresh.com/schedulingTimesCustomDates
Scheduling Times - Default
Concerns the default week-to-week scheduling entries for time-based Scheduling modules (ex. Franchise & Employee hours)
HTTP Request
http://crm.oxifresh.com/schedulingTimesDefaultWeekly
Scheduling Assignments - Custom Dates
Concerns the one-off, specific date entries for assignment-based Scheduling modules (ex. Zones, Equipment, Categories)
HTTP Request
http://crm.oxifresh.com/schedulingAssignmentsCustomDates
Scheduling Assignments - Default
Concerns the default week-to-week scheduling entries for assignment-based Scheduling modules (ex. Zones, Equipment, Categories)
HTTP Request
http://crm.oxifresh.com/schedulingAssignmentsDefaultWeekly
Services
The app-level list of Service items that all Franchises can potentially use.
HTTP Request
http://crm.oxifresh.com/services
Services Settings
The franchisee-specific prices and durations for each app-level Service. These IDs are whats listed in Invoice Line Items service_id
HTTP Request
http://crm.oxifresh.com/servicesSettings
Single Sign On
Concerns the Signle Sign On modules available for Employees to utilize in Scheduling Center
HTTP Request
http://crm.oxifresh.com/singleSignOns
Todos
Concerns the list of Todos assigned to specific Employees (or to the entire Franchise)
HTTP Request
http://crm.oxifresh.com/todos