Concepts
Idempotency: retries that cannot double-bill
A report costs a credit and a network can always time out between your request and our
answer. The Idempotency-Key
header is how a retry becomes the same request rather than a second one.
The contract
Choose a key that names the intent, such as your claim number, and send it on the first attempt and every retry. Two requests carrying the same key are one request:
- The first attempt does the work and answers normally, a 201 or a 202.
- A retry answers 200 with the same body and an
Idempotent-Replay: trueheader, and creates and bills nothing. - A retry that arrives while the first attempt is still running answers 409; wait and retry with the same key.
- The same key with a different method, path or body is a 409: a key names one request, and reusing it for another is always a bug worth hearing about.
Keys are honoured for 24 hours, scoped to your team and to the endpoint. Replays re-read the record as it stands, so retrying a create after the record was deleted answers with what the create made, and never re-bills. Errors are never stored: a 422 or a 402 on the first attempt leaves the key unused, and the retry runs fresh.
Which endpoints honour the header is stated in the reference beside each one. The handful of creating endpoints that do not are the ones with a natural key of their own, such as a property's address, where a retry is already safe; each says so in its reference entry.
// One stable key per intent, decided before the first attempt.
const key = `claim-${claim.id}-first-report`;
async function createReport(payload) {
for (let attempt = 1; attempt <= 3; attempt++) {
const response = await fetch(`${BASE}/api/v1/reports`, {
method: 'POST',
headers: {
...auth,
'Idempotency-Key': key,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
// 202 the first time, 200 with Idempotent-Replay on a retry.
if (response.ok) return response.json();
// 409: the first attempt is still in flight. Wait, then retry
// with the same key; never mint a new one.
await new Promise((resolve) => setTimeout(resolve, attempt * 2000));
}
throw new Error('report creation did not settle in three attempts');
}
Worked: creating a report
Create a report
POST /api/v1/reports
Ask for a report. Answers 202; poll links.self for the result.
reports:write Idempotency-Key honoured
Body
address
string
required
The property address, as a person would write it. Located for you; the response says where it resolved.
window_start
string
required
The first day of the period to search, YYYY-MM-DD. Must be in the past.
window_end
string
required
The last day of the period, on or after window_start.
radius_miles
number
How far around the address to search, 1 to 100. The plan may narrow it; window.narrowed_by_plan says so.
reference
string
Your own file or claim number, carried on the report unchanged.
recipient
string
Who the document is prepared for, printed on the cover.
property_id
string
A saved property to file this report under.
layout
array
Which sections, imagery and map layers the document is composed with. Requires a plan that can compose layouts; otherwise a 402 names the tier.
Answers
200
Replayed. This Idempotency-Key was honoured before; the earlier answer is returned again, with an Idempotent-Replay: true header.
202
Accepted. The report exists but is not yet generated; poll links.self for the result.
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/reports \
-H "Authorization: Bearer $TITAN_API_TOKEN" \
-H "Idempotency-Key: claim-48211-first-report" \
-H "Content-Type: application/json" \
-d '{
"address": "1428 Cedar Ridge Dr, Plano, TX 75023",
"window_start": "2016-08-23",
"window_end": "2026-08-23",
"radius_miles": 5
}'
$titan = new \GuzzleHttp\Client([
'base_uri' => 'https://titanweather.com',
'headers' => ['Authorization' => 'Bearer '.getenv('TITAN_API_TOKEN')],
]);
$response = $titan->post('/api/v1/reports', [
'headers' => ['Idempotency-Key' => 'claim-48211-first-report'],
'json' => [
'address' => '1428 Cedar Ridge Dr, Plano, TX 75023',
'window_start' => '2016-08-23',
'window_end' => '2026-08-23',
'radius_miles' => 5,
],
]);
$report = json_decode((string) $response->getBody(), true);
const response = await fetch('https://titanweather.com/api/v1/reports', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TITAN_API_TOKEN}`,
'Idempotency-Key': 'claim-48211-first-report',
'Content-Type': 'application/json',
},
body: JSON.stringify({
address: '1428 Cedar Ridge Dr, Plano, TX 75023',
window_start: '2016-08-23',
window_end: '2026-08-23',
radius_miles: 5,
}),
});
const report = await response.json();
import os
import requests
response = requests.post(
"https://titanweather.com/api/v1/reports",
headers={
"Authorization": f"Bearer {os.environ['TITAN_API_TOKEN']}",
"Idempotency-Key": "claim-48211-first-report",
},
json={
"address": "1428 Cedar Ridge Dr, Plano, TX 75023",
"window_start": "2016-08-23",
"window_end": "2026-08-23",
"radius_miles": 5,
},
)
report = response.json()
{
"data": {
"id": "0198b6c1-22e4-7f88-a1c9-5d40b7e2913a",
"object": "report",
"status": "pending",
"address": {
"query": "1428 Cedar Ridge Dr, Plano, TX 75023",
"street": null,
"city": null,
"state": null,
"postal_code": null,
"county": null,
"latitude": null,
"longitude": null
},
"window": {
"start": "2016-08-23",
"end": "2026-08-23",
"radius_miles": 5,
"narrowed_by_plan": false
},
"property_id": null,
"event_count": 0,
"observation_count": 0,
"coverage": {
"complete": false,
"provisional": false,
"summary": null,
"sources_searched": []
},
"headline": null,
"findings": null,
"failure_reason": null,
"plan_tier": "professional",
"layout": {
"sections": [
"summary",
"findings",
"map",
"events"
],
"imagery": [
"aerial",
"street_view"
],
"elements": [
"key_figures"
],
"map": null
},
"created_at": "2026-08-23T14:02:11Z",
"generated_at": null,
"pdf": {
"status": null,
"rendered_at": null
},
"links": {
"self": "https://titanweather.com/api/v1/reports/0198b6c1-22e4-7f88-a1c9-5d40b7e2913a",
"events": null,
"pdf": null
}
}
}