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.
| Limit | Value |
|---|---|
| Requests per minute | 60 |
| Requests per hour | 1,000 |
| Requests per day | 10,000 |
| Page size (`limit`) | 20 by default, 100 maximum |
| Operations in one batch call | 100 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?
| Method | Path | What it does | Permission |
|---|---|---|---|
| GET | /api/v1/customers | List customers with pagination and filtering | customers:read |
| POST | /api/v1/customers | Create a new customer | customers:create |
| POST | /api/v1/customers/batch | Perform batch operations on customers | customers:create, customers:update, customers:delete |
| POST | /api/v1/customers/check-email | Check if an email address is available for use | customers:read |
| POST | /api/v1/customers/search | Search and filter customers | customers:read |
| GET | /api/v1/customers/{id} | Get a customer by ID | customers:read |
| PUT | /api/v1/customers/{id} | Update a customer | customers:update |
| DELETE | /api/v1/customers/{id} | Soft delete a customer | customers:delete |
| GET | /api/v1/customers/{id}/addresses | Get all addresses for a customer | customers:read |
| POST | /api/v1/customers/{id}/addresses | Add a new address to a customer | customers:create |
| GET | /api/v1/customers/{id}/history | Get history of changes for a customer | customers:read |
| POST | /api/v1/customers/{id}/photos | Upload a photo to a customer | customers:update |
| Method | Path | What it does | Permission |
|---|---|---|---|
| GET | /api/v1/contacts | List contacts with pagination and filtering | contacts:read |
| POST | /api/v1/contacts | Create a new contact | contacts:create |
| POST | /api/v1/contacts/batch | Perform batch operations on contacts | contacts:create, contacts:update, contacts:delete |
| GET | /api/v1/contacts/check-email | Check if an email address is available (query parameter version) | contacts:read |
| POST | /api/v1/contacts/check-email | Check if an email address is available for use | contacts:read |
| POST | /api/v1/contacts/search | Search and filter contacts | contacts:read |
| GET | /api/v1/contacts/{id} | Get a single contact by ID or display ID | contacts:read |
| PUT | /api/v1/contacts/{id} | Update a contact | contacts:update |
| DELETE | /api/v1/contacts/{id} | Soft delete a contact | contacts:delete |
| GET | /api/v1/contacts/{id}/history | Get history of changes for a contact | contacts:read |
| POST | /api/v1/contacts/{id}/photos | Upload a photo to a contact | contacts:update |
| Method | Path | What it does | Permission |
|---|---|---|---|
| GET | /api/v1/materials | List materials with pagination and filtering | materials:read |
| POST | /api/v1/materials | Create a new material | materials:create |
| POST | /api/v1/materials/batch | Perform batch operations on multiple materials | materials:create, materials:update, materials:delete |
| PUT | /api/v1/materials/batch | PUT /api/v1/materials/batch | materials:create, materials:update, materials:delete |
| POST | /api/v1/materials/search | Search and filter materials | materials:read |
| GET | /api/v1/materials/{id} | Get a single material by ID or display ID | materials:read |
| PUT | /api/v1/materials/{id} | Update a material | materials:update |
| DELETE | /api/v1/materials/{id} | Soft delete a material | materials:delete |
| GET | /api/v1/materials/{id}/history | Get history of changes for a material | materials:read |
| POST | /api/v1/materials/{id}/photos | Upload a photo to a material | materials:update |
| Method | Path | What it does | Permission |
|---|---|---|---|
| GET | /api/v1/sites | List sites with pagination, filtering, and search | sites:read |
| POST | /api/v1/sites | Create a new site | sites:create |
| POST | /api/v1/sites/batch | Perform batch operations on sites | sites:create, sites:update, sites:delete |
| POST | /api/v1/sites/search | Search and filter sites | sites:read |
| GET | /api/v1/sites/{id} | Get a single site by ID | sites:read |
| PUT | /api/v1/sites/{id} | Update a site | sites:update |
| DELETE | /api/v1/sites/{id} | Delete or suspend a site | sites:delete |
| GET | /api/v1/sites/{id}/history | Get history of changes for a site | sites:read |
| POST | /api/v1/sites/{id}/photos | Upload a photo to a site | sites:update |
| Method | Path | What it does | Permission |
|---|---|---|---|
| GET | /api/v1/trucks | List trucks with pagination and filtering | trucks:read |
| POST | /api/v1/trucks | Create a new truck | trucks:create |
| POST | /api/v1/trucks/batch | Perform batch operations on trucks | trucks:create, trucks:update, trucks:delete |
| POST | /api/v1/trucks/check-vin | Check truck VIN availability | trucks:read |
| POST | /api/v1/trucks/search | Search and filter trucks | trucks:read |
| GET | /api/v1/trucks/{id} | Get truck by ID | trucks:read |
| PUT | /api/v1/trucks/{id} | Update truck | trucks:update |
| DELETE | /api/v1/trucks/{id} | Delete truck | trucks:delete |
| GET | /api/v1/trucks/{id}/history | Get truck history | trucks:read |
| POST | /api/v1/trucks/{id}/photos | Upload a photo to a truck | trucks: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.
Related pages
- ProductWhat Quote360 does today: material quotes with haul and tax on every line, route pricing, quote stage workflows, branded proposal PDFs and a REST API.
- PricingQuote360 is free during early access, with up to 10 users per company workspace. Paid plans will be announced at least 30 days before anything is charged.
- SecurityHow Quote360 protects your data: where it is stored, which providers process it, how access is controlled, and how to report a vulnerability.
- ContactWrite to the Quote360 team about quoting heavy construction materials, an account question, a security report or the press.