Developers

Quote360 REST API for developers

The Quote360 REST API is a JSON API over your company's customers, contacts, materials, sites and trucks, authenticated with an API key that acts with its owner's role permissions.

Last updated:

How do I authenticate?

Create an API key in the application and send it on every request. A key belongs to one company, starts with `sk_live_`, is stored only as a hash, and can be restricted to specific IP addresses and origins. It acts with the permissions of the role its owner holds, so a key made by a viewer can only read.

  • Authorization: Bearer <key> - the header to prefer
  • Orange-API-Key: <key> - accepted for existing integrations

What are the limits?

Limits are per key. A request over a limit is answered 429.

Defaults per API key
LimitValue
Requests per minute60
Requests per hour1,000
Requests per day10,000
Page size (`limit`)20 by default, 100 maximum
Operations in one batch call100 maximum

Idempotency: send `X-Idempotency-Key` on material create, update, delete and batch to make a retry safe.

How does pagination work?

List endpoints take `page` and `limit` and answer with the rows plus a pagination block, so a client can walk the pages without cursors.

Which operations does the API have?

customers · 12 operations
MethodPathWhat it doesPermission
GET/api/v1/customersList customers with pagination and filteringcustomers:read
POST/api/v1/customersCreate a new customercustomers:create
POST/api/v1/customers/batchPerform batch operations on customerscustomers:create, customers:update, customers:delete
POST/api/v1/customers/check-emailCheck if an email address is available for usecustomers:read
POST/api/v1/customers/searchSearch and filter customerscustomers:read
GET/api/v1/customers/{id}Get a customer by IDcustomers:read
PUT/api/v1/customers/{id}Update a customercustomers:update
DELETE/api/v1/customers/{id}Soft delete a customercustomers:delete
GET/api/v1/customers/{id}/addressesGet all addresses for a customercustomers:read
POST/api/v1/customers/{id}/addressesAdd a new address to a customercustomers:create
GET/api/v1/customers/{id}/historyGet history of changes for a customercustomers:read
POST/api/v1/customers/{id}/photosUpload a photo to a customercustomers:update
contacts · 11 operations
MethodPathWhat it doesPermission
GET/api/v1/contactsList contacts with pagination and filteringcontacts:read
POST/api/v1/contactsCreate a new contactcontacts:create
POST/api/v1/contacts/batchPerform batch operations on contactscontacts:create, contacts:update, contacts:delete
GET/api/v1/contacts/check-emailCheck if an email address is available (query parameter version)contacts:read
POST/api/v1/contacts/check-emailCheck if an email address is available for usecontacts:read
POST/api/v1/contacts/searchSearch and filter contactscontacts:read
GET/api/v1/contacts/{id}Get a single contact by ID or display IDcontacts:read
PUT/api/v1/contacts/{id}Update a contactcontacts:update
DELETE/api/v1/contacts/{id}Soft delete a contactcontacts:delete
GET/api/v1/contacts/{id}/historyGet history of changes for a contactcontacts:read
POST/api/v1/contacts/{id}/photosUpload a photo to a contactcontacts:update
materials · 10 operations
MethodPathWhat it doesPermission
GET/api/v1/materialsList materials with pagination and filteringmaterials:read
POST/api/v1/materialsCreate a new materialmaterials:create
POST/api/v1/materials/batchPerform batch operations on multiple materialsmaterials:create, materials:update, materials:delete
PUT/api/v1/materials/batchPUT /api/v1/materials/batchmaterials:create, materials:update, materials:delete
POST/api/v1/materials/searchSearch and filter materialsmaterials:read
GET/api/v1/materials/{id}Get a single material by ID or display IDmaterials:read
PUT/api/v1/materials/{id}Update a materialmaterials:update
DELETE/api/v1/materials/{id}Soft delete a materialmaterials:delete
GET/api/v1/materials/{id}/historyGet history of changes for a materialmaterials:read
POST/api/v1/materials/{id}/photosUpload a photo to a materialmaterials:update
sites · 9 operations
MethodPathWhat it doesPermission
GET/api/v1/sitesList sites with pagination, filtering, and searchsites:read
POST/api/v1/sitesCreate a new sitesites:create
POST/api/v1/sites/batchPerform batch operations on sitessites:create, sites:update, sites:delete
POST/api/v1/sites/searchSearch and filter sitessites:read
GET/api/v1/sites/{id}Get a single site by IDsites:read
PUT/api/v1/sites/{id}Update a sitesites:update
DELETE/api/v1/sites/{id}Delete or suspend a sitesites:delete
GET/api/v1/sites/{id}/historyGet history of changes for a sitesites:read
POST/api/v1/sites/{id}/photosUpload a photo to a sitesites:update
trucks · 10 operations
MethodPathWhat it doesPermission
GET/api/v1/trucksList trucks with pagination and filteringtrucks:read
POST/api/v1/trucksCreate a new trucktrucks:create
POST/api/v1/trucks/batchPerform batch operations on truckstrucks:create, trucks:update, trucks:delete
POST/api/v1/trucks/check-vinCheck truck VIN availabilitytrucks:read
POST/api/v1/trucks/searchSearch and filter truckstrucks:read
GET/api/v1/trucks/{id}Get truck by IDtrucks:read
PUT/api/v1/trucks/{id}Update trucktrucks:update
DELETE/api/v1/trucks/{id}Delete trucktrucks:delete
GET/api/v1/trucks/{id}/historyGet truck historytrucks:read
POST/api/v1/trucks/{id}/photosUpload a photo to a trucktrucks:update

How do I call it?

GET /api/v1/customers?page=1&limit=20

List customers

cURL
curl -X GET "https://www.quote360.app/api/v1/customers?page=1&limit=20" \
  -H "Authorization: Bearer $QUOTE360_API_KEY" \
  -H "Accept: application/json"
JavaScript
const response = await fetch("https://www.quote360.app/api/v1/customers?page=1&limit=20", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.QUOTE360_API_KEY}`,
  },
});
const data = await response.json();
Python
import os, requests
response = requests.get(
    "https://www.quote360.app/api/v1/customers?page=1&limit=20",
    headers={"Authorization": f"Bearer {os.environ['QUOTE360_API_KEY']}"},
    timeout=30,
)
data = response.json()
PHP
<?php
$ch = curl_init("https://www.quote360.app/api/v1/customers?page=1&limit=20");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Authorization: Bearer " . getenv("QUOTE360_API_KEY"),
]);
$data = json_decode(curl_exec($ch), true);
Ruby
require "net/http"
require "json"
uri = URI("https://www.quote360.app/api/v1/customers?page=1&limit=20")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("QUOTE360_API_KEY")}"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
data = JSON.parse(response.body)
Rust
use reqwest::Client;
let client = Client::new();
let response = client
    .get("https://www.quote360.app/api/v1/customers?page=1&limit=20")
    .bearer_auth(std::env::var("QUOTE360_API_KEY")?)
    .send()
    .await?;
let data: serde_json::Value = response.json().await?;

POST /api/v1/materials

Create a material

cURL
curl -X POST "https://www.quote360.app/api/v1/materials" \
  -H "Authorization: Bearer $QUOTE360_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Class II Aggregate Base",
  "materialType": "Aggregates",
  "uom": "TON",
  "taxable": true
}'
JavaScript
const response = await fetch("https://www.quote360.app/api/v1/materials", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.QUOTE360_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Class II Aggregate Base",
    "materialType": "Aggregates",
    "uom": "TON",
    "taxable": true
  }),
});
const data = await response.json();
Python
import os, requests
response = requests.post(
    "https://www.quote360.app/api/v1/materials",
    headers={"Authorization": f"Bearer {os.environ['QUOTE360_API_KEY']}"},
    json={
      "name": "Class II Aggregate Base",
      "materialType": "Aggregates",
      "uom": "TON",
      "taxable": true
    },
    timeout=30,
)
data = response.json()
PHP
<?php
$ch = curl_init("https://www.quote360.app/api/v1/materials");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Authorization: Bearer " . getenv("QUOTE360_API_KEY"),
    "Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{
  "name": "Class II Aggregate Base",
  "materialType": "Aggregates",
  "uom": "TON",
  "taxable": true
}');
$data = json_decode(curl_exec($ch), true);
Ruby
require "net/http"
require "json"
uri = URI("https://www.quote360.app/api/v1/materials")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("QUOTE360_API_KEY")}"
request["Content-Type"] = "application/json"
request.body = "{\n  \"name\": \"Class II Aggregate Base\",\n  \"materialType\": \"Aggregates\",\n  \"uom\": \"TON\",\n  \"taxable\": true\n}"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
data = JSON.parse(response.body)
Rust
use reqwest::Client;
let client = Client::new();
let response = client
    .post("https://www.quote360.app/api/v1/materials")
    .bearer_auth(std::env::var("QUOTE360_API_KEY")?)
    .json(&serde_json::json!({
      "name": "Class II Aggregate Base",
      "materialType": "Aggregates",
      "uom": "TON",
      "taxable": true
    }))
    .send()
    .await?;
let data: serde_json::Value = response.json().await?;

Questions and answers

Can a key be limited to read-only access?
Give the key's owner a role with read permissions only. Keys act with their owner's role permissions, so that is what limits them today.
Is there a quotes endpoint?
Not yet. The API covers customers, contacts, materials, sites and trucks; quotes are created in the application.