Reference
Measurements
Roof measurements over the API: facets in as plan-view rings with pitch and fall direction, classified linework, the waste table and the materials schedule out.
Create a measurement
POST /api/v1/measurements
Open a measurement on a property, optionally with its facets. The plan gate is a 402.
measurements:write Idempotency-Key honoured
Body
property
string
required
The saved property the roof belongs to, by id.
name
string
What the measurement is called on the property and in the document.
facets
array
The drawing, whole: one entry per roof plane. Each carries a key of your choosing, vertices as a plan-view ring of [longitude, latitude] corners, a pitch in inches of rise per foot of run, and a fall_azimuth in compass degrees naming the downhill direction. Send pitch and fall_azimuth as null to settle them later.
Answers
200
Replayed. This Idempotency-Key was honoured before; the earlier answer is returned again, with an Idempotent-Replay: true header.
201
Created. Totals are resolved before this answers.
409
This Idempotency-Key was already used for a different request, or its first request is still being processed. A retry must resend the request it is retrying.
Plus the shared refusals: 401, 402, 403, 404, 422 and 429, described on Errors.
curl -X POST https://titanweather.com/api/v1/measurements \
-H "Authorization: Bearer $TITAN_API_TOKEN" \
-H "Idempotency-Key: richardson-roof-v1" \
-H "Content-Type: application/json" \
-d '{
"property": "0198a3f2-6d7e-7b41-92c5-2f6a1c9d4e88",
"name": "Main house",
"facets": [
{
"key": "f1",
"vertices": [
[
-96.7726,
32.9107
],
[
-96.772446,
32.9107
],
[
-96.772446,
32.910764
],
[
-96.7726,
32.910764
]
],
"pitch": 6,
"fall_azimuth": 180
}
]
}'
$titan = new \GuzzleHttp\Client([
'base_uri' => 'https://titanweather.com',
'headers' => ['Authorization' => 'Bearer '.getenv('TITAN_API_TOKEN')],
]);
$response = $titan->post('/api/v1/measurements', [
'headers' => ['Idempotency-Key' => 'richardson-roof-v1'],
'json' => [
'property' => '0198a3f2-6d7e-7b41-92c5-2f6a1c9d4e88',
'name' => 'Main house',
'facets' => [
[
'key' => 'f1',
'vertices' => [
[
-96.7726,
32.9107,
],
[
-96.772446,
32.9107,
],
[
-96.772446,
32.910764,
],
[
-96.7726,
32.910764,
],
],
'pitch' => 6,
'fall_azimuth' => 180,
],
],
],
]);
$measurement = json_decode((string) $response->getBody(), true);
const response = await fetch('https://titanweather.com/api/v1/measurements', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TITAN_API_TOKEN}`,
'Idempotency-Key': 'richardson-roof-v1',
'Content-Type': 'application/json',
},
body: JSON.stringify({
property: '0198a3f2-6d7e-7b41-92c5-2f6a1c9d4e88',
name: 'Main house',
facets: [
{
key: 'f1',
vertices: [
[
-96.7726,
32.9107,
],
[
-96.772446,
32.9107,
],
[
-96.772446,
32.910764,
],
[
-96.7726,
32.910764,
],
],
pitch: 6,
fall_azimuth: 180,
},
],
}),
});
const measurement = await response.json();
import os
import requests
response = requests.post(
"https://titanweather.com/api/v1/measurements",
headers={
"Authorization": f"Bearer {os.environ['TITAN_API_TOKEN']}",
"Idempotency-Key": "richardson-roof-v1",
},
json={
"property": "0198a3f2-6d7e-7b41-92c5-2f6a1c9d4e88",
"name": "Main house",
"facets": [
{
"key": "f1",
"vertices": [
[
-96.7726,
32.9107,
],
[
-96.772446,
32.9107,
],
[
-96.772446,
32.910764,
],
[
-96.7726,
32.910764,
],
],
"pitch": 6,
"fall_azimuth": 180,
},
],
},
)
measurement = response.json()
{
"data": {
"id": "0198f1a4-2c67-7d92-8e41-b53a90cd7f16",
"object": "measurement",
"name": "Main house",
"property_id": "0198a3f2-6d7e-7b41-92c5-2f6a1c9d4e88",
"status": "complete",
"source": "drawn",
"proposal": {
"outcome": null,
"detail": null
},
"totals": {
"squares": 25.2,
"plan_area_sqft": 2254.4,
"roof_area_sqft": 2520.4,
"facet_count": 2,
"predominant_pitch": 6,
"edges_ft": {
"ridge": 47.8,
"hip": 0,
"valley": 0,
"eave": 95.6,
"rake": 52.8,
"flashing": 0,
"step_flashing": 0,
"parapet": 0,
"transition": 0
}
},
"waste": {
"suggested_pct": 10,
"chosen_pct": null,
"effective_pct": 10
},
"material": {
"key": "architectural_shingle",
"label": "Architectural asphalt shingle"
},
"verified": {
"by": "Dana Whitfield",
"at": "2026-08-23T20:12:41Z"
},
"algorithm_version": 1,
"created_at": "2026-08-23T19:58:03Z",
"updated_at": "2026-08-23T20:12:41Z",
"links": {
"self": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16",
"pdf": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/pdf",
"owner_pdf": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/pdf?variant=owner",
"xml": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/xml",
"order": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/order",
"property": "https://titanweather.com/api/v1/properties/0198a3f2-6d7e-7b41-92c5-2f6a1c9d4e88"
}
}
}
Quote a drawing
POST /api/v1/measurements/quote
What a drawing measures, totals and classified linework, without saving it.
measurements:write
Body
facets
array
required
The drawing, whole: one entry per roof plane. Each carries a key of your choosing, vertices as a plan-view ring of [longitude, latitude] corners, a pitch in inches of rise per foot of run, and a fall_azimuth in compass degrees naming the downhill direction. Send pitch and fall_azimuth as null to settle them later.
Answers
200
The answer.
Plus the shared refusals: 401, 402, 403, 404, 422 and 429, described on Errors.
curl -X POST https://titanweather.com/api/v1/measurements/quote \
-H "Authorization: Bearer $TITAN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"facets": [
{
"key": "f1",
"vertices": [
[
-96.7726,
32.9107
],
[
-96.772446,
32.9107
],
[
-96.772446,
32.910764
],
[
-96.7726,
32.910764
]
],
"pitch": 6,
"fall_azimuth": 180
}
]
}'
$titan = new \GuzzleHttp\Client([
'base_uri' => 'https://titanweather.com',
'headers' => ['Authorization' => 'Bearer '.getenv('TITAN_API_TOKEN')],
]);
$response = $titan->post('/api/v1/measurements/quote', [
'json' => [
'facets' => [
[
'key' => 'f1',
'vertices' => [
[
-96.7726,
32.9107,
],
[
-96.772446,
32.9107,
],
[
-96.772446,
32.910764,
],
[
-96.7726,
32.910764,
],
],
'pitch' => 6,
'fall_azimuth' => 180,
],
],
],
]);
$quote = json_decode((string) $response->getBody(), true);
const response = await fetch('https://titanweather.com/api/v1/measurements/quote', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TITAN_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
facets: [
{
key: 'f1',
vertices: [
[
-96.7726,
32.9107,
],
[
-96.772446,
32.9107,
],
[
-96.772446,
32.910764,
],
[
-96.7726,
32.910764,
],
],
pitch: 6,
fall_azimuth: 180,
},
],
}),
});
const quote = await response.json();
import os
import requests
response = requests.post(
"https://titanweather.com/api/v1/measurements/quote",
headers={
"Authorization": f"Bearer {os.environ['TITAN_API_TOKEN']}",
},
json={
"facets": [
{
"key": "f1",
"vertices": [
[
-96.7726,
32.9107,
],
[
-96.772446,
32.9107,
],
[
-96.772446,
32.910764,
],
[
-96.7726,
32.910764,
],
],
"pitch": 6,
"fall_azimuth": 180,
},
],
},
)
quote = response.json()
{
"object": "measurement_quote",
"totals": {
"squares": 25.2,
"plan_area_sqft": 2254.4,
"roof_area_sqft": 2520.4,
"facet_count": 2,
"predominant_pitch": 6,
"edges_ft": {
"ridge": 47.8,
"hip": 0,
"valley": 0,
"eave": 95.6,
"rake": 52.8,
"flashing": 0,
"step_flashing": 0,
"parapet": 0,
"transition": 0
}
},
"facets": [
{
"key": "f1",
"label": "A",
"pitch": 6,
"fall_azimuth": 180,
"plan_area_sqft": 1127.2,
"roof_area_sqft": 1260.2
},
[
"..."
]
],
"edges": [
{
"key": "0f4c2a9d1e8b73650a4f9c2d8e1b7364",
"kind": "ridge",
"a": [
-96.7726,
32.910764
],
"b": [
-96.772446,
32.910764
],
"plan_ft": 47.8,
"roof_ft": 47.8,
"classified": "auto",
"step": false,
"lower_kind": null
},
[
"..."
]
],
"unsettled_ft": 0,
"bounds": {
"west": -96.7726,
"south": 32.9107,
"east": -96.772446,
"north": 32.910764
}
}
List measurements
GET /api/v1/measurements
The roofs the team has measured, with the trade's totals on each.
measurements:read paginated
Query parameters
property
string
Only measurements on this saved property.
status
enum
Only measurements in this state.
Answers
200
The answer.
Plus the shared refusals: 401, 402, 403, 404, 422 and 429, described on Errors.
curl https://titanweather.com/api/v1/measurements?status=complete \
-H "Authorization: Bearer $TITAN_API_TOKEN"
$titan = new \GuzzleHttp\Client([
'base_uri' => 'https://titanweather.com',
'headers' => ['Authorization' => 'Bearer '.getenv('TITAN_API_TOKEN')],
]);
$response = $titan->get('/api/v1/measurements', [
'query' => [
'status' => 'complete',
],
]);
$measurements = json_decode((string) $response->getBody(), true);
const response = await fetch('https://titanweather.com/api/v1/measurements?status=complete', {
headers: {
Authorization: `Bearer ${process.env.TITAN_API_TOKEN}`,
},
});
const measurements = await response.json();
import os
import requests
response = requests.get(
"https://titanweather.com/api/v1/measurements",
headers={
"Authorization": f"Bearer {os.environ['TITAN_API_TOKEN']}",
},
params={
"status": "complete",
},
)
measurements = response.json()
{
"data": [
{
"id": "0198f1a4-2c67-7d92-8e41-b53a90cd7f16",
"object": "measurement",
"name": "Main house",
"property_id": "0198a3f2-6d7e-7b41-92c5-2f6a1c9d4e88",
"status": "complete",
"source": "drawn",
"proposal": {
"outcome": null,
"detail": null
},
"totals": {
"squares": 25.2,
"plan_area_sqft": 2254.4,
"roof_area_sqft": 2520.4,
"facet_count": 2,
"predominant_pitch": 6,
"edges_ft": {
"ridge": 47.8,
"hip": 0,
"valley": 0,
"eave": 95.6,
"rake": 52.8,
"flashing": 0,
"step_flashing": 0,
"parapet": 0,
"transition": 0
}
},
"waste": {
"suggested_pct": 10,
"chosen_pct": null,
"effective_pct": 10
},
"material": {
"key": "architectural_shingle",
"label": "Architectural asphalt shingle"
},
"verified": {
"by": "Dana Whitfield",
"at": "2026-08-23T20:12:41Z"
},
"algorithm_version": 1,
"created_at": "2026-08-23T19:58:03Z",
"updated_at": "2026-08-23T20:12:41Z",
"links": {
"self": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16",
"pdf": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/pdf",
"owner_pdf": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/pdf?variant=owner",
"xml": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/xml",
"order": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/order",
"property": "https://titanweather.com/api/v1/properties/0198a3f2-6d7e-7b41-92c5-2f6a1c9d4e88"
}
}
],
"links": {
"first": "https://titanweather.com/api/v1/measurements?page=1",
"last": "https://titanweather.com/api/v1/measurements?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "https://titanweather.com/api/v1/measurements",
"per_page": 25,
"to": 1,
"total": 1
}
}
Retrieve a measurement
GET /api/v1/measurements/{measurement}
One measurement whole: facets, classified linework, buildings, features, ventilation, the reconstruction audit, areas per pitch, the waste table and the materials schedule.
measurements:read
Path parameters
measurement
The id.
Answers
200
The answer.
Plus the shared refusals: 401, 402, 403, 404, 422 and 429, described on Errors.
curl https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16 \
-H "Authorization: Bearer $TITAN_API_TOKEN"
$titan = new \GuzzleHttp\Client([
'base_uri' => 'https://titanweather.com',
'headers' => ['Authorization' => 'Bearer '.getenv('TITAN_API_TOKEN')],
]);
$response = $titan->get('/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16');
$measurement = json_decode((string) $response->getBody(), true);
const response = await fetch('https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16', {
headers: {
Authorization: `Bearer ${process.env.TITAN_API_TOKEN}`,
},
});
const measurement = await response.json();
import os
import requests
response = requests.get(
"https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16",
headers={
"Authorization": f"Bearer {os.environ['TITAN_API_TOKEN']}",
},
)
measurement = response.json()
{
"data": {
"id": "0198f1a4-2c67-7d92-8e41-b53a90cd7f16",
"object": "measurement",
"name": "Main house",
"property_id": "0198a3f2-6d7e-7b41-92c5-2f6a1c9d4e88",
"status": "complete",
"source": "drawn",
"proposal": {
"outcome": null,
"detail": null
},
"totals": {
"squares": 25.2,
"plan_area_sqft": 2254.4,
"roof_area_sqft": 2520.4,
"facet_count": 2,
"predominant_pitch": 6,
"edges_ft": {
"ridge": 47.8,
"hip": 0,
"valley": 0,
"eave": 95.6,
"rake": 52.8,
"flashing": 0,
"step_flashing": 0,
"parapet": 0,
"transition": 0
}
},
"waste": {
"suggested_pct": 10,
"chosen_pct": null,
"effective_pct": 10
},
"material": {
"key": "architectural_shingle",
"label": "Architectural asphalt shingle"
},
"verified": {
"by": "Dana Whitfield",
"at": "2026-08-23T20:12:41Z"
},
"algorithm_version": 1,
"created_at": "2026-08-23T19:58:03Z",
"updated_at": "2026-08-23T20:12:41Z",
"links": {
"self": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16",
"pdf": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/pdf",
"owner_pdf": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/pdf?variant=owner",
"xml": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/xml",
"order": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/order",
"property": "https://titanweather.com/api/v1/properties/0198a3f2-6d7e-7b41-92c5-2f6a1c9d4e88"
},
"fingerprint": "CC16-DB82-DD5B",
"facets": [
{
"key": "f1",
"label": "A",
"ring": [
[
-96.7726,
32.9107
],
[
-96.772446,
32.9107
],
[
-96.772446,
32.910764
],
[
-96.7726,
32.910764
]
],
"pitch": 6,
"fall_azimuth": 180,
"plan_area_sqft": 1127.2,
"roof_area_sqft": 1260.2,
"confidence": null,
"reviewed": true
},
[
"..."
]
],
"edges": [
{
"key": "0f4c2a9d1e8b73650a4f9c2d8e1b7364",
"kind": "ridge",
"a": [
-96.7726,
32.910764
],
"b": [
-96.772446,
32.910764
],
"plan_ft": 47.8,
"roof_ft": 47.8,
"classified": "auto",
"step": false,
"lower_kind": null
},
[
"..."
]
],
"structures": [
{
"key": "main",
"name": "Main building",
"role": "main",
"stories": 1,
"outline": [
[
-96.7726,
32.9107
],
[
-96.772446,
32.9107
],
[
-96.772446,
32.910764
],
[
-96.7726,
32.910764
]
],
"footprint_sqft": 2254.4,
"plan_area_sqft": 2254.4,
"roof_area_sqft": 2520.4,
"facet_count": 2,
"eave_height_m": 3.1,
"ridge_height_m": 5.4
}
],
"features": [
{
"key": "3b1e7c0a9d5f4e2b8a6c1d0f7e9b3a51",
"kind": "chimney",
"facet_key": "f1",
"ring": [
[
-96.77255,
32.91072
],
[
-96.77254,
32.91072
],
[
-96.77254,
32.910728
],
[
-96.77255,
32.910728
]
],
"point": null,
"width_ft": 3,
"depth_ft": 2.5,
"diameter_in": null,
"mount": null,
"source": "drawn",
"confidence": null,
"reviewed": true
},
[
"..."
]
],
"ventilation": {
"attic_area_sqft": 2254,
"ratio": 300,
"exhaust_sqin": 541,
"intake_sqin": 541,
"exhaust": "ridge_vent",
"...": "..."
},
"audit": [
{
"label": "Engine",
"value": "USA Structures footprint with 3DEP lidar"
},
[
"..."
]
],
"areas_per_pitch": [
{
"pitch": "6/12",
"area_sqft": 2520,
"share": 100
}
],
"waste_table": [
{
"pct": 0,
"area_sqft": 2520,
"squares": 25.2,
"suggested": false
},
{
"pct": 10,
"area_sqft": 2772,
"squares": 27.7,
"suggested": true
},
[
"..."
]
],
"materials": {
"preset": "GAF Timberline HDZ",
"brand": "GAF",
"waste_pct": 10,
"wasted_sqft": 2772,
"wasted_squares": 27.7,
"lines": [
{
"key": "shingles",
"label": "GAF Timberline HDZ",
"quantity": 84,
"unit": "bundles",
"detail": "2,520 sq ft + 10% waste = 27.7 squares ร 3 bundles a square",
"product_key": "gaf-timberline-hdz",
"supplier_sku": "GAF-HDZ-CHAR",
"unit_price": 42.5,
"line_total": 3570
},
[
"..."
]
],
"pricing": {
"currency": "USD",
"subtotal": 5412.75,
"priced": 9,
"unpriced": 2,
"quoted_on": "2026-09-01",
"supplier": "srs"
},
"notes": [
"Waste applies to the field area only; starter, ridge cap and drip edge are cut to the measured feet."
]
},
"material_tables": {
"columns": [
0,
10,
12,
15,
17,
20,
22
],
"chosen_pct": 10,
"preferred": {
"shingles": "gaf-timberline-hdz",
"...": "..."
},
"steep": [
{
"key": "shingles",
"label": "Shingle products",
"rows": [
{
"key": "gaf-timberline-hdz",
"label": "GAF Timberline HDZ",
"unit": "bundle",
"brand": "gaf",
"preferred": true,
"verified": true,
"application": null,
"quantities": {
"0": 76,
"10": 84,
"...": "..."
}
},
[
"..."
]
]
},
[
"..."
]
],
"metals": [
{
"label": "Metals & flashing",
"rows": [
{
"key": "eave-drip-edge",
"label": "Eave drip edge (10 ft)",
"unit": "piece",
"brand": null,
"preferred": false,
"verified": true,
"application": null,
"quantities": {
"0": 8,
"10": 9,
"...": "..."
}
},
[
"..."
]
]
}
],
"low_slope": null
}
}
}
Replace the drawing
POST /api/v1/measurements/{measurement}/facets
Replace the drawing whole. Edges are re-derived; a person's classifications survive by segment.
measurements:write
Path parameters
measurement
The id.
Body
facets
array
required
The drawing, whole: one entry per roof plane. Each carries a key of your choosing, vertices as a plan-view ring of [longitude, latitude] corners, a pitch in inches of rise per foot of run, and a fall_azimuth in compass degrees naming the downhill direction. Send pitch and fall_azimuth as null to settle them later.
Answers
200
The answer.
Plus the shared refusals: 401, 402, 403, 404, 422 and 429, described on Errors.
curl -X POST https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/facets \
-H "Authorization: Bearer $TITAN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"facets": [
{
"key": "f1",
"vertices": [
[
-96.7726,
32.9107
],
[
-96.772446,
32.9107
],
[
-96.772446,
32.910764
],
[
-96.7726,
32.910764
]
],
"pitch": 6,
"fall_azimuth": 180
}
]
}'
$titan = new \GuzzleHttp\Client([
'base_uri' => 'https://titanweather.com',
'headers' => ['Authorization' => 'Bearer '.getenv('TITAN_API_TOKEN')],
]);
$response = $titan->post('/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/facets', [
'json' => [
'facets' => [
[
'key' => 'f1',
'vertices' => [
[
-96.7726,
32.9107,
],
[
-96.772446,
32.9107,
],
[
-96.772446,
32.910764,
],
[
-96.7726,
32.910764,
],
],
'pitch' => 6,
'fall_azimuth' => 180,
],
],
],
]);
$measurement = json_decode((string) $response->getBody(), true);
const response = await fetch('https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/facets', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TITAN_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
facets: [
{
key: 'f1',
vertices: [
[
-96.7726,
32.9107,
],
[
-96.772446,
32.9107,
],
[
-96.772446,
32.910764,
],
[
-96.7726,
32.910764,
],
],
pitch: 6,
fall_azimuth: 180,
},
],
}),
});
const measurement = await response.json();
import os
import requests
response = requests.post(
"https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/facets",
headers={
"Authorization": f"Bearer {os.environ['TITAN_API_TOKEN']}",
},
json={
"facets": [
{
"key": "f1",
"vertices": [
[
-96.7726,
32.9107,
],
[
-96.772446,
32.9107,
],
[
-96.772446,
32.910764,
],
[
-96.7726,
32.910764,
],
],
"pitch": 6,
"fall_azimuth": 180,
},
],
},
)
measurement = response.json()
{
"data": {
"id": "0198f1a4-2c67-7d92-8e41-b53a90cd7f16",
"object": "measurement",
"name": "Main house",
"property_id": "0198a3f2-6d7e-7b41-92c5-2f6a1c9d4e88",
"status": "complete",
"source": "drawn",
"proposal": {
"outcome": null,
"detail": null
},
"totals": {
"squares": 25.2,
"plan_area_sqft": 2254.4,
"roof_area_sqft": 2520.4,
"facet_count": 2,
"predominant_pitch": 6,
"edges_ft": {
"ridge": 47.8,
"hip": 0,
"valley": 0,
"eave": 95.6,
"rake": 52.8,
"flashing": 0,
"step_flashing": 0,
"parapet": 0,
"transition": 0
}
},
"waste": {
"suggested_pct": 10,
"chosen_pct": null,
"effective_pct": 10
},
"material": {
"key": "architectural_shingle",
"label": "Architectural asphalt shingle"
},
"verified": {
"by": "Dana Whitfield",
"at": "2026-08-23T20:12:41Z"
},
"algorithm_version": 1,
"created_at": "2026-08-23T19:58:03Z",
"updated_at": "2026-08-23T20:12:41Z",
"links": {
"self": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16",
"pdf": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/pdf",
"owner_pdf": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/pdf?variant=owner",
"xml": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/xml",
"order": "https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/order",
"property": "https://titanweather.com/api/v1/properties/0198a3f2-6d7e-7b41-92c5-2f6a1c9d4e88"
}
}
}
Download the document
GET /api/v1/measurements/{measurement}/pdf
The measurement document, as bytes; 202 while it is still being rendered. variant=owner is the property owner's copy, the same drawing without the trade's pages.
measurements:read
Path parameters
measurement
The id.
Query parameters
variant
enum
Which copy: the whole report (default), or the property owner's copy without the trade's pages.
Answers
200
The rendered document, as bytes.
202
The document is being rendered in the background. The body carries status: preparing and retry_after; poll this endpoint, or pdf.status on the report.
503
The rendering engine is unavailable. An outage of ours, not a billing problem; retry later.
Plus the shared refusals: 401, 402, 403, 404, 422 and 429, described on Errors.
curl https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/pdf \
-H "Authorization: Bearer $TITAN_API_TOKEN" \
-o measurement.pdf
$titan = new \GuzzleHttp\Client([
'base_uri' => 'https://titanweather.com',
'headers' => ['Authorization' => 'Bearer '.getenv('TITAN_API_TOKEN')],
]);
$response = $titan->get('/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/pdf', [
'sink' => 'measurement.pdf',
]);
const response = await fetch('https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/pdf', {
headers: {
Authorization: `Bearer ${process.env.TITAN_API_TOKEN}`,
},
});
const bytes = await response.arrayBuffer();
import os
import requests
response = requests.get(
"https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/pdf",
headers={
"Authorization": f"Bearer {os.environ['TITAN_API_TOKEN']}",
},
)
with open("measurement.pdf", "wb") as file:
file.write(response.content)
The measurement document, as application/pdf bytes: the diagrams, lengths, areas per pitch, the waste table and the materials schedule. The document is rendered in the background after the drawing changes; until it is in, this answers 202 with {"object": "pdf", "status": "preparing", "retry_after": 10} and a Retry-After header. Poll, or read the measurement itself.
Download the XML
GET /api/v1/measurements/{measurement}/xml
The measurement as XML: structures of points, faces and typed lines, features, flashing, ventilation, materials and the audit.
measurements:read
Path parameters
measurement
The id.
Answers
200
The measurement as XML, written on request.
Plus the shared refusals: 401, 402, 403, 404, 422 and 429, described on Errors.
curl https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/xml \
-H "Authorization: Bearer $TITAN_API_TOKEN" \
-o measurement.xml
$titan = new \GuzzleHttp\Client([
'base_uri' => 'https://titanweather.com',
'headers' => ['Authorization' => 'Bearer '.getenv('TITAN_API_TOKEN')],
]);
$response = $titan->get('/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/xml', [
'sink' => 'measurement.xml',
]);
const response = await fetch('https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/xml', {
headers: {
Authorization: `Bearer ${process.env.TITAN_API_TOKEN}`,
},
});
const bytes = await response.arrayBuffer();
import os
import requests
response = requests.get(
"https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/xml",
headers={
"Authorization": f"Bearer {os.environ['TITAN_API_TOKEN']}",
},
)
with open("measurement.xml", "wb") as file:
file.write(response.content)
The measurement as application/xml: STRUCTURES each holding a ROOF of POINTS, FACES and LINES typed EAVE, RAKE, RIDGE, HIP, VALLEY, FLASHING, STEPFLASH, TRANSITION, PARAPET or OTHER, then FEATURES, FLASHING, VENTILATION, MATERIALS and AUDIT. Every corner is written once per structure and referenced by id from the faces and lines. Written on request; never 202.
Download an order sheet
GET /api/v1/measurements/{measurement}/order
The bill of materials as a CSV in an order desk's column layout, with the branch item numbers the team's price book holds.
measurements:read
Path parameters
measurement
The id.
Query parameters
supplier
enum
The order desk's column layout. Defaults to the team's own choice; generic carries every column.
Answers
200
The bill of materials as an order-sheet CSV, written on request.
Plus the shared refusals: 401, 402, 403, 404, 422 and 429, described on Errors.
curl https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/order?supplier=generic \
-H "Authorization: Bearer $TITAN_API_TOKEN" \
-o order.csv
$titan = new \GuzzleHttp\Client([
'base_uri' => 'https://titanweather.com',
'headers' => ['Authorization' => 'Bearer '.getenv('TITAN_API_TOKEN')],
]);
$response = $titan->get('/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/order', [
'query' => [
'supplier' => 'generic',
],
'sink' => 'order.csv',
]);
const response = await fetch('https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/order?supplier=generic', {
headers: {
Authorization: `Bearer ${process.env.TITAN_API_TOKEN}`,
},
});
const bytes = await response.arrayBuffer();
import os
import requests
response = requests.get(
"https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16/order",
headers={
"Authorization": f"Bearer {os.environ['TITAN_API_TOKEN']}",
},
params={
"supplier": "generic",
},
)
with open("order.csv", "wb") as file:
file.write(response.content)
The working bill as text/csv, one row a line, in the named order desk's column layout: the branch item number the team's price book holds (or our product key), description, quantity, unit, and on the generic sheet the unit price and line total. Written on request; never 202.
Delete a measurement
DELETE /api/v1/measurements/{measurement}
Remove a measurement.
measurements:write
Path parameters
measurement
The id.
Answers
204
Deleted. The stored document goes with it.
Plus the shared refusals: 401, 402, 403, 404, 422 and 429, described on Errors.
curl -X DELETE https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16 \
-H "Authorization: Bearer $TITAN_API_TOKEN"
$titan = new \GuzzleHttp\Client([
'base_uri' => 'https://titanweather.com',
'headers' => ['Authorization' => 'Bearer '.getenv('TITAN_API_TOKEN')],
]);
$response = $titan->delete('/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16');
$response = json_decode((string) $response->getBody(), true);
const response = await fetch('https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.TITAN_API_TOKEN}`,
},
});
const response = await response.json();
import os
import requests
response = requests.delete(
"https://titanweather.com/api/v1/measurements/0198f1a4-2c67-7d92-8e41-b53a90cd7f16",
headers={
"Authorization": f"Bearer {os.environ['TITAN_API_TOKEN']}",
},
)
response = response.json()
No body. The measurement, its drawing and its stored document are gone.