Jump to an endpoint
Reference
HailMate API reference
Jobs, contacts, tasks, money, photos, door knocks and hail history for your HailMate workspace. Requests and responses are JSON. Lists are newest first and paged with a cursor. New to the API? Start with the overview and quickstart.
- Base URL
https://app.hailmate.ai/api/v1- Authentication
Authorization: Bearer hm_live_…on every request. Keys- OpenAPI 3.1
- openapi.yaml · openapi.json
Endpoints
Connection
Check a key and see which workspace it belongs to.
Check a key
/pingThe cheapest authenticated call. Returns the workspace the key belongs to and what the key may do. /me is the same endpoint.
curl https://app.hailmate.ai/api/v1/ping \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/ping', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/ping",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"ok": true,
"object": "workspace",
"workspace_id": "5f0c2a8e-1b3d-4c7a-9e21-7a3b8d4c6e10",
"workspace": "Ridgeline Roofing",
"api_version": "v1",
"key": {
"name": "Zapier",
"access": "write"
}
}Endpoints
Jobs
The storm-restoration jobs on your pipeline boards.
List jobs
/jobsNewest first. Archived jobs are left out unless you ask for them.
Query parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.updated_sincestringdate-timeOnly records changed at or after this ISO date or date-time. The way to sync only what moved.
created_afterstringdate-timeOnly records created at or after this ISO date or date-time.
created_beforestringdate-timeOnly records created before this ISO date or date-time.
stagestringA stage
keyfrom/stages.pipeline_idstringuuidjob_typestringinsuranceretailassigned_tostringuuidA user id. Matches the primary assignee OR any co-assignee.
contact_idstringuuidJobs where this contact is the homeowner, the adjuster, or a secondary contact.
archivedstringOmitted, only live jobs.
truereturns archived jobs,anyreturns both.trueany
curl "https://app.hailmate.ai/api/v1/jobs?stage=claim_approved&limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/jobs?stage=claim_approved&limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/jobs",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"stage": "claim_approved", "limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"object": "job",
"job_number": "JOB-00142",
"name": "1804 Cedar Ridge Dr",
"job_type": "insurance",
"stage": "inspection_scheduled",
"stage_label": "Inspection Scheduled",
"stage_is_completed": false,
"stage_is_lost": false,
"stage_entered_at": "2026-09-16T14:02:00.000Z",
"pipeline_id": "1cf7d87b-7d99-4ba0-b277-0e88ac127e75",
"pipeline_name": "Insurance",
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"county": "Collin",
"homeowner_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"homeowner": {
"id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"name": "Dana Reed",
"email": "dana@example.com",
"phone": "(972) 555-0148"
},
"adjuster_id": null,
"adjuster": null,
"secondary_contact_ids": [],
"secondary_adjuster_ids": [],
"assigned_to_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"assigned_to_name": "Sam Carter",
"assignee_ids": [
"5d0e1c2b-7777-4000-8000-0000000000ab"
],
"assignee_names": [
"Sam Carter"
],
"insurance_company": "Example Mutual",
"claim_number": "CLM-88213",
"policy_number": null,
"date_of_loss": "2026-09-12",
"damage_types": [
"hail"
],
"tags": [],
"lead_source": "Website",
"priority": "normal",
"mortgage_company": null,
"financing_method": null,
"rcv_amount": 24380.5,
"acv_amount": 19240,
"deductible": 2500,
"supplements_amount": null,
"estimated_amount": null,
"final_amount": null,
"total_job_value": 24380.5,
"amount_received": 0,
"balance_due": 24380.5,
"contract_date": null,
"installation_date": null,
"archived": false,
"completed_at": null,
"lost_at": null,
"lost_reason": null,
"created_at": "2026-09-16T14:02:00.000Z",
"updated_at": "2026-09-16T14:02:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa",
"portal_url": "https://app.hailmate.ai/project/0f0f0f0f-3333-4000-8000-0000000000cc"
}
]
}Create a job
/jobsOnly name and address are required. With no stage the job lands on the first column of your default pipeline. stage may be a stage key or its label as your board shows it ("Claim Approved"). notes, when sent, becomes the first note on the job's timeline.
A homeowner, adjuster or assignee named by id must belong to this workspace. The new job fires job.created.
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
Request bodyapplication/json
namestringRequiredjob_nameworks too.Up to 200 characters
addressstringRequiredproperty_addressworks too.Up to 300 characters
citystringstatestringA code or a name — "TX" or "Texas".
postal_codestringzipworks too.countystringjob_typestringinsuranceretailDefault
insurancestagestringA stage key or label. Defaults to the first stage of the default pipeline.
pipeline_idstringuuidhomeowner_idstringuuidadjuster_idstringuuidassigned_tostringuuidA teammate's user id from
/users.lead_sourcestringprioritystringlownormalhighurgentinsurance_companystringclaim_numberstringpolicy_numberstringdate_of_lossstringdatemortgage_companystringfinancing_methodstringcashcheckcredit_cardfinancingotherrcv_amountnumberacv_amountnumberdeductiblenumbersupplements_amountnumberestimated_amountnumberfinal_amountnumbercontract_datestringdateinstallation_datestringdatedamage_typesarray of stringOr a comma-separated string.
tagsarray of stringOr a comma-separated string.
notesstringBecomes the first note on the job.
Returns
201Created. A Job object.
- 400
invalid_requestSomething in the request was wrong;fieldnames what. - 403
forbiddenA read-only key tried to change something. - 409
conflictAn Idempotency-Key clash — reused for a different request, or the first is still running.
Any request can also answer 401, 402, 429 or 500 — errors.
curl -X POST https://app.hailmate.ai/api/v1/jobs \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "1804 Cedar Ridge Dr",
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"job_type": "insurance",
"stage": "Inspection Scheduled",
"homeowner_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"lead_source": "Website",
"insurance_company": "Example Mutual",
"claim_number": "CLM-88213",
"notes": "Homeowner reports hail on the back slope."
}'import { randomUUID } from 'node:crypto';
const response = await fetch('https://app.hailmate.ai/api/v1/jobs', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({
name: '1804 Cedar Ridge Dr',
address: '1804 Cedar Ridge Dr',
city: 'Plano',
state: 'TX',
postal_code: '75024',
job_type: 'insurance',
stage: 'Inspection Scheduled',
homeowner_id: 'b7c2d3e4-4444-4000-8000-0000000000dd',
lead_source: 'Website',
insurance_company: 'Example Mutual',
claim_number: 'CLM-88213',
notes: 'Homeowner reports hail on the back slope.',
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import uuid
import requests
response = requests.post(
"https://app.hailmate.ai/api/v1/jobs",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"name": "1804 Cedar Ridge Dr",
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"job_type": "insurance",
"stage": "Inspection Scheduled",
"homeowner_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"lead_source": "Website",
"insurance_company": "Example Mutual",
"claim_number": "CLM-88213",
"notes": "Homeowner reports hail on the back slope.",
},
)
response.raise_for_status()
data = response.json(){
"id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"object": "job",
"job_number": "JOB-00142",
"name": "1804 Cedar Ridge Dr",
"job_type": "insurance",
"stage": "inspection_scheduled",
"stage_label": "Inspection Scheduled",
"stage_is_completed": false,
"stage_is_lost": false,
"stage_entered_at": "2026-09-16T14:02:00.000Z",
"pipeline_id": "1cf7d87b-7d99-4ba0-b277-0e88ac127e75",
"pipeline_name": "Insurance",
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"county": "Collin",
"homeowner_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"homeowner": {
"id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"name": "Dana Reed",
"email": "dana@example.com",
"phone": "(972) 555-0148"
},
"adjuster_id": null,
"adjuster": null,
"secondary_contact_ids": [],
"secondary_adjuster_ids": [],
"assigned_to_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"assigned_to_name": "Sam Carter",
"assignee_ids": [
"5d0e1c2b-7777-4000-8000-0000000000ab"
],
"assignee_names": [
"Sam Carter"
],
"insurance_company": "Example Mutual",
"claim_number": "CLM-88213",
"policy_number": null,
"date_of_loss": "2026-09-12",
"damage_types": [
"hail"
],
"tags": [],
"lead_source": "Website",
"priority": "normal",
"mortgage_company": null,
"financing_method": null,
"rcv_amount": 24380.5,
"acv_amount": 19240,
"deductible": 2500,
"supplements_amount": null,
"estimated_amount": null,
"final_amount": null,
"total_job_value": 24380.5,
"amount_received": 0,
"balance_due": 24380.5,
"contract_date": null,
"installation_date": null,
"archived": false,
"completed_at": null,
"lost_at": null,
"lost_reason": null,
"created_at": "2026-09-16T14:02:00.000Z",
"updated_at": "2026-09-16T14:02:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa",
"portal_url": "https://app.hailmate.ai/project/0f0f0f0f-3333-4000-8000-0000000000cc"
}Find jobs
/jobs/searchaddress and name match part of the value ("1804 Cedar" finds "1804 Cedar Ridge Dr"); job_number and claim_number must match whole (case-insensitive). With no criteria the result is EMPTY — never "the newest jobs", which is how a blank search step writes to the wrong job.
Query parameters
addressstringjob_numberstringclaim_numberstringnamestringcontact_idstringuuidlimitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50
curl "https://app.hailmate.ai/api/v1/jobs/search?address=1804%20Cedar" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/jobs/search?address=1804%20Cedar', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/jobs/search",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"address": "1804 Cedar"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"object": "job",
"job_number": "JOB-00142",
"name": "1804 Cedar Ridge Dr",
"job_type": "insurance",
"stage": "inspection_scheduled",
"stage_label": "Inspection Scheduled",
"stage_is_completed": false,
"stage_is_lost": false,
"stage_entered_at": "2026-09-16T14:02:00.000Z",
"pipeline_id": "1cf7d87b-7d99-4ba0-b277-0e88ac127e75",
"pipeline_name": "Insurance",
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"county": "Collin",
"homeowner_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"homeowner": {
"id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"name": "Dana Reed",
"email": "dana@example.com",
"phone": "(972) 555-0148"
},
"adjuster_id": null,
"adjuster": null,
"secondary_contact_ids": [],
"secondary_adjuster_ids": [],
"assigned_to_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"assigned_to_name": "Sam Carter",
"assignee_ids": [
"5d0e1c2b-7777-4000-8000-0000000000ab"
],
"assignee_names": [
"Sam Carter"
],
"insurance_company": "Example Mutual",
"claim_number": "CLM-88213",
"policy_number": null,
"date_of_loss": "2026-09-12",
"damage_types": [
"hail"
],
"tags": [],
"lead_source": "Website",
"priority": "normal",
"mortgage_company": null,
"financing_method": null,
"rcv_amount": 24380.5,
"acv_amount": 19240,
"deductible": 2500,
"supplements_amount": null,
"estimated_amount": null,
"final_amount": null,
"total_job_value": 24380.5,
"amount_received": 0,
"balance_due": 24380.5,
"contract_date": null,
"installation_date": null,
"archived": false,
"completed_at": null,
"lost_at": null,
"lost_reason": null,
"created_at": "2026-09-16T14:02:00.000Z",
"updated_at": "2026-09-16T14:02:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa",
"portal_url": "https://app.hailmate.ai/project/0f0f0f0f-3333-4000-8000-0000000000cc"
}
]
}Get a job
/jobs/{id}curl https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"object": "job",
"job_number": "JOB-00142",
"name": "1804 Cedar Ridge Dr",
"job_type": "insurance",
"stage": "inspection_scheduled",
"stage_label": "Inspection Scheduled",
"stage_is_completed": false,
"stage_is_lost": false,
"stage_entered_at": "2026-09-16T14:02:00.000Z",
"pipeline_id": "1cf7d87b-7d99-4ba0-b277-0e88ac127e75",
"pipeline_name": "Insurance",
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"county": "Collin",
"homeowner_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"homeowner": {
"id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"name": "Dana Reed",
"email": "dana@example.com",
"phone": "(972) 555-0148"
},
"adjuster_id": null,
"adjuster": null,
"secondary_contact_ids": [],
"secondary_adjuster_ids": [],
"assigned_to_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"assigned_to_name": "Sam Carter",
"assignee_ids": [
"5d0e1c2b-7777-4000-8000-0000000000ab"
],
"assignee_names": [
"Sam Carter"
],
"insurance_company": "Example Mutual",
"claim_number": "CLM-88213",
"policy_number": null,
"date_of_loss": "2026-09-12",
"damage_types": [
"hail"
],
"tags": [],
"lead_source": "Website",
"priority": "normal",
"mortgage_company": null,
"financing_method": null,
"rcv_amount": 24380.5,
"acv_amount": 19240,
"deductible": 2500,
"supplements_amount": null,
"estimated_amount": null,
"final_amount": null,
"total_job_value": 24380.5,
"amount_received": 0,
"balance_due": 24380.5,
"contract_date": null,
"installation_date": null,
"archived": false,
"completed_at": null,
"lost_at": null,
"lost_reason": null,
"created_at": "2026-09-16T14:02:00.000Z",
"updated_at": "2026-09-16T14:02:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa",
"portal_url": "https://app.hailmate.ai/project/0f0f0f0f-3333-4000-8000-0000000000cc"
}Update a job
/jobs/{id}Send only the fields to change. A field sent as null (or "") is cleared; a field left out is untouched. name and address cannot be cleared.
Moving a job is {"stage": "Claim Approved"} — a key or a label. It runs every stage automation, writes the timeline entry and fires job.stage_changed, exactly as dragging the card does. Setting assigned_to makes that teammate the primary assignee.
Path parameters
idstringuuidRequired
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
Request bodyapplication/json
namestringaddressstringcitystring | nullstatestring | nullpostal_codestring | nullcountystring | nulljob_typestringinsuranceretailstagestringpipeline_idstringuuidhomeowner_idstring | nulluuidadjuster_idstring | nulluuidassigned_tostring | nulluuidlead_sourcestring | nullprioritystring | nulllownormalhighurgentinsurance_companystring | nullclaim_numberstring | nullpolicy_numberstring | nulldate_of_lossstring | nulldatemortgage_companystring | nullfinancing_methodstring | nullrcv_amountnumber | nullacv_amountnumber | nulldeductiblenumber | nullsupplements_amountnumber | nullestimated_amountnumber | nullfinal_amountnumber | nullcontract_datestring | nulldateinstallation_datestring | nulldatedamage_typesarray of string | nulltagsarray of string | nullarchivedbooleanlost_reasonstring | null
curl -X PATCH https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"stage": "claim_approved",
"rcv_amount": 24380.5,
"assigned_to": "5d0e1c2b-7777-4000-8000-0000000000ab"
}'const response = await fetch('https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
stage: 'claim_approved',
rcv_amount: 24380.5,
assigned_to: '5d0e1c2b-7777-4000-8000-0000000000ab',
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.patch(
"https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
json={
"stage": "claim_approved",
"rcv_amount": 24380.5,
"assigned_to": "5d0e1c2b-7777-4000-8000-0000000000ab",
},
)
response.raise_for_status()
data = response.json(){
"id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"object": "job",
"job_number": "JOB-00142",
"name": "1804 Cedar Ridge Dr",
"job_type": "insurance",
"stage": "inspection_scheduled",
"stage_label": "Inspection Scheduled",
"stage_is_completed": false,
"stage_is_lost": false,
"stage_entered_at": "2026-09-16T14:02:00.000Z",
"pipeline_id": "1cf7d87b-7d99-4ba0-b277-0e88ac127e75",
"pipeline_name": "Insurance",
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"county": "Collin",
"homeowner_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"homeowner": {
"id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"name": "Dana Reed",
"email": "dana@example.com",
"phone": "(972) 555-0148"
},
"adjuster_id": null,
"adjuster": null,
"secondary_contact_ids": [],
"secondary_adjuster_ids": [],
"assigned_to_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"assigned_to_name": "Sam Carter",
"assignee_ids": [
"5d0e1c2b-7777-4000-8000-0000000000ab"
],
"assignee_names": [
"Sam Carter"
],
"insurance_company": "Example Mutual",
"claim_number": "CLM-88213",
"policy_number": null,
"date_of_loss": "2026-09-12",
"damage_types": [
"hail"
],
"tags": [],
"lead_source": "Website",
"priority": "normal",
"mortgage_company": null,
"financing_method": null,
"rcv_amount": 24380.5,
"acv_amount": 19240,
"deductible": 2500,
"supplements_amount": null,
"estimated_amount": null,
"final_amount": null,
"total_job_value": 24380.5,
"amount_received": 0,
"balance_due": 24380.5,
"contract_date": null,
"installation_date": null,
"archived": false,
"completed_at": null,
"lost_at": null,
"lost_reason": null,
"created_at": "2026-09-16T14:02:00.000Z",
"updated_at": "2026-09-16T14:02:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa",
"portal_url": "https://app.hailmate.ai/project/0f0f0f0f-3333-4000-8000-0000000000cc"
}Delete a job
/jobs/{id}The job goes to Settings → Recently Deleted for 30 days with everything on it, and can be restored from there. Fires job.deleted.
Path parameters
idstringuuidRequired
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
curl -X DELETE https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.delete(
"https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"object": "job",
"deleted": true
}List a job's notes
/jobs/{id}/notesPath parameters
idstringuuidRequired
Query parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.
curl "https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/notes?limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/notes?limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/notes",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "3c8b1a07-6666-4000-8000-0000000000b4",
"object": "note",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"content": "Adjuster meeting moved to Thursday 10am. Homeowner will be home.",
"author_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"author_name": "Sam Carter",
"created_at": "2026-09-27T14:20:00.000Z",
"updated_at": "2026-09-27T14:20:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa"
}
]
}List a job's photos and documents
/jobs/{id}/filesPath parameters
idstringuuidRequired
Query parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.categorystringphotodocumentscopeestimatecontractother
curl "https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/files?limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/files?limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/files",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "7e2d9c40-8888-4000-8000-0000000000a5",
"object": "file",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"name": "north-slope-hits.jpg",
"category": "photo",
"mime_type": "image/jpeg",
"is_photo": true,
"is_video": false,
"size_bytes": 1532410,
"description": "Hail hits on the north slope, chalked",
"tags": [
"damage",
"north slope"
],
"latitude": 33.0726,
"longitude": -96.7512,
"taken_at": "2026-09-16T15:12:08.000Z",
"uploaded_by_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"uploaded_by_name": "Sam Carter",
"download_url": "https://files.example-cdn.com/jobs/f2b1a0c4/north-slope-hits.jpg",
"created_at": "2026-09-16T15:12:40.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa"
}
]
}List a job's photos
/jobs/{id}/photos/jobs/{id}/files?category=photo, as its own path.
Path parameters
idstringuuidRequired
Query parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.
curl "https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/photos?limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/photos?limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/photos",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "7e2d9c40-8888-4000-8000-0000000000a5",
"object": "file",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"name": "north-slope-hits.jpg",
"category": "photo",
"mime_type": "image/jpeg",
"is_photo": true,
"is_video": false,
"size_bytes": 1532410,
"description": "Hail hits on the north slope, chalked",
"tags": [
"damage",
"north slope"
],
"latitude": 33.0726,
"longitude": -96.7512,
"taken_at": "2026-09-16T15:12:08.000Z",
"uploaded_by_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"uploaded_by_name": "Sam Carter",
"download_url": "https://files.example-cdn.com/jobs/f2b1a0c4/north-slope-hits.jpg",
"created_at": "2026-09-16T15:12:40.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa"
}
]
}List a job's tasks and appointments
/jobs/{id}/tasksPath parameters
idstringuuidRequired
Query parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.
curl "https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/tasks?limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/tasks?limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/tasks",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "d4e5f6a7-5555-4000-8000-0000000000ee",
"object": "task",
"title": "Adjuster meeting — 1804 Cedar Ridge Dr",
"description": null,
"due_date": "2026-10-02T15:00:00",
"end_date": null,
"duration_minutes": 60,
"priority": "normal",
"completed": false,
"completed_at": null,
"is_appointment": true,
"appointment_type": "adjuster_meeting",
"outcome": null,
"customer_reminder": "sms",
"reminder_minutes": 60,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"contact_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"assigned_to_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"assigned_to_name": "Sam Carter",
"created_at": "2026-09-16T14:10:00.000Z",
"updated_at": "2026-09-16T14:10:00.000Z",
"url": "https://app.hailmate.ai/task/d4e5f6a7-5555-4000-8000-0000000000ee"
}
]
}List a job's estimates
/jobs/{id}/estimatesPath parameters
idstringuuidRequired
Query parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.
curl "https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/estimates?limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/estimates?limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/estimates",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "a41c9e20-2222-4000-8000-0000000000e1",
"object": "estimate",
"estimate_number": "EST-0214",
"title": "Roof replacement — 1804 Cedar Ridge Dr",
"document_type": "proposal",
"status": "sent",
"signature_status": "pending",
"total": 21900,
"package_totals": {
"good": 18450,
"better": 21900,
"best": 26400
},
"upgrades_total": 1250,
"selected_tier": null,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"valid_until": "2026-10-28T00:00:00.000Z",
"sent_at": "2026-09-28T15:10:00.000Z",
"first_viewed_at": "2026-09-28T18:42:00.000Z",
"last_viewed_at": "2026-09-28T19:05:00.000Z",
"view_count": 3,
"signed_at": null,
"signer_name": null,
"pdf_url": "https://files.example-cdn.com/estimates/EST-0214.pdf",
"signed_pdf_url": null,
"created_at": "2026-09-27T21:30:00.000Z",
"updated_at": "2026-09-28T19:05:00.000Z",
"url": "https://app.hailmate.ai/estimates/a41c9e20-2222-4000-8000-0000000000e1",
"view_url": "https://app.hailmate.ai/estimate/6f1d3b52-9c7e-4a10-8f3b-2d4e5a6b7c8d"
}
]
}List a job's invoices
/jobs/{id}/invoicesPath parameters
idstringuuidRequired
Query parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.
curl "https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/invoices?limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/invoices?limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/invoices",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "c90d7b31-3333-4000-8000-0000000000f2",
"object": "invoice",
"invoice_number": "INV-0311",
"title": "Deductible",
"status": "sent",
"purpose": "deductible",
"subtotal": 2500,
"discount_amount": 0,
"tax_amount": 0,
"late_fee_amount": 0,
"total_amount": 2500,
"amount_paid": 0,
"balance_due": 2500,
"part_number": null,
"part_count": null,
"issue_date": "2026-09-28",
"due_date": "2026-10-12",
"sent_at": "2026-09-28T16:00:00.000Z",
"first_viewed_at": null,
"paid_at": null,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"contact_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"estimate_id": null,
"pdf_url": "https://files.example-cdn.com/invoices/INV-0311.pdf",
"created_at": "2026-09-28T15:55:00.000Z",
"updated_at": "2026-09-28T16:00:00.000Z",
"url": "https://app.hailmate.ai/jobs/invoice/c90d7b31-3333-4000-8000-0000000000f2",
"payment_url": "https://app.hailmate.ai/invoice/0b7e2c19-5d4a-4f3e-9a1b-8c6d2e4f1a37"
}
]
}List a job's payments
/jobs/{id}/paymentsPath parameters
idstringuuidRequired
Query parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.
curl "https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/payments?limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/payments?limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/jobs/f2b1a0c4-1111-4000-8000-0000000000aa/payments",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "9fa3e1d2-5555-4000-8000-0000000000c3",
"object": "payment",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"invoice_id": null,
"amount": 13164,
"payment_type": "acv",
"payer_type": "insurance",
"payment_method": "check",
"status": "received",
"check_number": "4471",
"date_received": "2026-09-26",
"date_deposited": null,
"notes": "First ACV check from Example Mutual",
"is_refund": false,
"refund_of_payment_id": null,
"online": false,
"created_at": "2026-09-26T20:14:00.000Z",
"updated_at": "2026-09-26T20:14:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa"
}
]
}Endpoints
Contacts
Homeowners, adjusters, subcontractors and everyone else in your book.
List contacts
/contactsQuery parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.updated_sincestringdate-timeOnly records changed at or after this ISO date or date-time. The way to sync only what moved.
created_afterstringdate-timeOnly records created at or after this ISO date or date-time.
created_beforestringdate-timeOnly records created before this ISO date or date-time.
typestringhomeownercommercialadjustercontractorsubcontractorinstall_crewinspectormortgage_companyotherassigned_tostringuuid
curl "https://app.hailmate.ai/api/v1/contacts?limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/contacts?limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/contacts",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"object": "contact",
"first_name": "Dana",
"last_name": "Reed",
"full_name": "Dana Reed",
"email": "dana@example.com",
"phone": "(972) 555-0148",
"company": null,
"type": "homeowner",
"trades": [],
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"notes": null,
"claim_number": null,
"adjuster_type": null,
"adjuster_extension": null,
"assigned_to_id": null,
"assigned_to_name": null,
"created_at": "2026-09-16T13:40:00.000Z",
"updated_at": "2026-09-16T13:40:00.000Z",
"url": "https://app.hailmate.ai/contact/b7c2d3e4-4444-4000-8000-0000000000dd"
}
]
}Create a contact
/contactsAt least one of a name, email, phone or company is required — a contact with none of them is refused rather than filling your CRM with blank rows. Send name alone and it is split into first and last.
US phone numbers are stored as (615) 555-0148, the way your team types them. sms_consent cannot be set: no integration can establish that a homeowner agreed to be texted.
To avoid duplicates, search first — GET /contacts/search matches a phone number however it was typed.
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
Request bodyapplication/json
namestringA full name, split into first and last when those are not sent.
first_namestringlast_namestringemailstringemailphonestringAny format.
companystringtypestringhomeownercommercialadjustercontractorsubcontractorinstall_crewinspectormortgage_companyothertradesarray of stringaddressstringcitystringstatestringpostal_codestringnotesstringclaim_numberstringadjuster_typestringadjuster_extensionstringassigned_tostringuuid
curl -X POST https://app.hailmate.ai/api/v1/contacts \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"first_name": "Dana",
"last_name": "Reed",
"email": "dana@example.com",
"phone": "+19725550148",
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024"
}'import { randomUUID } from 'node:crypto';
const response = await fetch('https://app.hailmate.ai/api/v1/contacts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({
first_name: 'Dana',
last_name: 'Reed',
email: 'dana@example.com',
phone: '+19725550148',
address: '1804 Cedar Ridge Dr',
city: 'Plano',
state: 'TX',
postal_code: '75024',
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import uuid
import requests
response = requests.post(
"https://app.hailmate.ai/api/v1/contacts",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"first_name": "Dana",
"last_name": "Reed",
"email": "dana@example.com",
"phone": "+19725550148",
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
},
)
response.raise_for_status()
data = response.json(){
"id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"object": "contact",
"first_name": "Dana",
"last_name": "Reed",
"full_name": "Dana Reed",
"email": "dana@example.com",
"phone": "(972) 555-0148",
"company": null,
"type": "homeowner",
"trades": [],
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"notes": null,
"claim_number": null,
"adjuster_type": null,
"adjuster_extension": null,
"assigned_to_id": null,
"assigned_to_name": null,
"created_at": "2026-09-16T13:40:00.000Z",
"updated_at": "2026-09-16T13:40:00.000Z",
"url": "https://app.hailmate.ai/contact/b7c2d3e4-4444-4000-8000-0000000000dd"
}Find contacts
/contacts/searchTries email first, then phone, then name, and returns the first that matches. A phone number matches however it was typed into HailMate — +16155550148, 615-555-0148 and (615) 555-0148 are the same number. name matches a first name, last name, "first last" or a company. With no criteria the result is empty.
Query parameters
emailstringphonestringnamestringlimitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50
curl "https://app.hailmate.ai/api/v1/contacts/search?phone=972-555-0148" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/contacts/search?phone=972-555-0148', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/contacts/search",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"phone": "972-555-0148"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"object": "contact",
"first_name": "Dana",
"last_name": "Reed",
"full_name": "Dana Reed",
"email": "dana@example.com",
"phone": "(972) 555-0148",
"company": null,
"type": "homeowner",
"trades": [],
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"notes": null,
"claim_number": null,
"adjuster_type": null,
"adjuster_extension": null,
"assigned_to_id": null,
"assigned_to_name": null,
"created_at": "2026-09-16T13:40:00.000Z",
"updated_at": "2026-09-16T13:40:00.000Z",
"url": "https://app.hailmate.ai/contact/b7c2d3e4-4444-4000-8000-0000000000dd"
}
]
}Get a contact
/contacts/{id}curl https://app.hailmate.ai/api/v1/contacts/b7c2d3e4-4444-4000-8000-0000000000dd \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/contacts/b7c2d3e4-4444-4000-8000-0000000000dd', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/contacts/b7c2d3e4-4444-4000-8000-0000000000dd",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"object": "contact",
"first_name": "Dana",
"last_name": "Reed",
"full_name": "Dana Reed",
"email": "dana@example.com",
"phone": "(972) 555-0148",
"company": null,
"type": "homeowner",
"trades": [],
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"notes": null,
"claim_number": null,
"adjuster_type": null,
"adjuster_extension": null,
"assigned_to_id": null,
"assigned_to_name": null,
"created_at": "2026-09-16T13:40:00.000Z",
"updated_at": "2026-09-16T13:40:00.000Z",
"url": "https://app.hailmate.ai/contact/b7c2d3e4-4444-4000-8000-0000000000dd"
}Update a contact
/contacts/{id}Send only the fields to change; null clears a field.
Path parameters
idstringuuidRequired
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
Request bodyapplication/json
first_namestring | nulllast_namestring | nullemailstring | nullphonestring | nullcompanystring | nulltypestringhomeownercommercialadjustercontractorsubcontractorinstall_crewinspectormortgage_companyothertradesarray of string | nulladdressstring | nullcitystring | nullstatestring | nullpostal_codestring | nullnotesstring | nullclaim_numberstring | nulladjuster_typestring | nulladjuster_extensionstring | nullassigned_tostring | nulluuid
curl -X PATCH https://app.hailmate.ai/api/v1/contacts/b7c2d3e4-4444-4000-8000-0000000000dd \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "dana.reed@example.com",
"notes": "Prefers texts after 5pm."
}'const response = await fetch('https://app.hailmate.ai/api/v1/contacts/b7c2d3e4-4444-4000-8000-0000000000dd', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'dana.reed@example.com',
notes: 'Prefers texts after 5pm.',
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.patch(
"https://app.hailmate.ai/api/v1/contacts/b7c2d3e4-4444-4000-8000-0000000000dd",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
json={
"email": "dana.reed@example.com",
"notes": "Prefers texts after 5pm.",
},
)
response.raise_for_status()
data = response.json(){
"id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"object": "contact",
"first_name": "Dana",
"last_name": "Reed",
"full_name": "Dana Reed",
"email": "dana@example.com",
"phone": "(972) 555-0148",
"company": null,
"type": "homeowner",
"trades": [],
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"notes": null,
"claim_number": null,
"adjuster_type": null,
"adjuster_extension": null,
"assigned_to_id": null,
"assigned_to_name": null,
"created_at": "2026-09-16T13:40:00.000Z",
"updated_at": "2026-09-16T13:40:00.000Z",
"url": "https://app.hailmate.ai/contact/b7c2d3e4-4444-4000-8000-0000000000dd"
}Delete a contact
/contacts/{id}Kept for 30 days under Settings → Recently Deleted. Fires contact.deleted.
curl -X DELETE https://app.hailmate.ai/api/v1/contacts/b7c2d3e4-4444-4000-8000-0000000000dd \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/contacts/b7c2d3e4-4444-4000-8000-0000000000dd', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.delete(
"https://app.hailmate.ai/api/v1/contacts/b7c2d3e4-4444-4000-8000-0000000000dd",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"object": "job",
"deleted": true
}List a contact's jobs
/contacts/{id}/jobsEvery job this person is on — as homeowner, adjuster or secondary contact — archived included.
Path parameters
idstringuuidRequired
Query parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.
curl "https://app.hailmate.ai/api/v1/contacts/b7c2d3e4-4444-4000-8000-0000000000dd/jobs?limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/contacts/b7c2d3e4-4444-4000-8000-0000000000dd/jobs?limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/contacts/b7c2d3e4-4444-4000-8000-0000000000dd/jobs",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"object": "job",
"job_number": "JOB-00142",
"name": "1804 Cedar Ridge Dr",
"job_type": "insurance",
"stage": "inspection_scheduled",
"stage_label": "Inspection Scheduled",
"stage_is_completed": false,
"stage_is_lost": false,
"stage_entered_at": "2026-09-16T14:02:00.000Z",
"pipeline_id": "1cf7d87b-7d99-4ba0-b277-0e88ac127e75",
"pipeline_name": "Insurance",
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"county": "Collin",
"homeowner_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"homeowner": {
"id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"name": "Dana Reed",
"email": "dana@example.com",
"phone": "(972) 555-0148"
},
"adjuster_id": null,
"adjuster": null,
"secondary_contact_ids": [],
"secondary_adjuster_ids": [],
"assigned_to_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"assigned_to_name": "Sam Carter",
"assignee_ids": [
"5d0e1c2b-7777-4000-8000-0000000000ab"
],
"assignee_names": [
"Sam Carter"
],
"insurance_company": "Example Mutual",
"claim_number": "CLM-88213",
"policy_number": null,
"date_of_loss": "2026-09-12",
"damage_types": [
"hail"
],
"tags": [],
"lead_source": "Website",
"priority": "normal",
"mortgage_company": null,
"financing_method": null,
"rcv_amount": 24380.5,
"acv_amount": 19240,
"deductible": 2500,
"supplements_amount": null,
"estimated_amount": null,
"final_amount": null,
"total_job_value": 24380.5,
"amount_received": 0,
"balance_due": 24380.5,
"contract_date": null,
"installation_date": null,
"archived": false,
"completed_at": null,
"lost_at": null,
"lost_reason": null,
"created_at": "2026-09-16T14:02:00.000Z",
"updated_at": "2026-09-16T14:02:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa",
"portal_url": "https://app.hailmate.ai/project/0f0f0f0f-3333-4000-8000-0000000000cc"
}
]
}Endpoints
Tasks
Tasks and appointments. An appointment is a task with an appointment_type.
List tasks and appointments
/tasks/appointments is the same list narrowed to appointments.
Query parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.updated_sincestringdate-timeOnly records changed at or after this ISO date or date-time. The way to sync only what moved.
created_afterstringdate-timeOnly records created at or after this ISO date or date-time.
created_beforestringdate-timeOnly records created before this ISO date or date-time.
completedstringtruefalseappointmentsstringtruereturns appointments only.truejob_idstringuuidcontact_idstringuuidassigned_tostringuuiddue_afterstringWall-clock, e.g.
2026-10-01or2026-10-01T08:00:00.due_beforestring
curl "https://app.hailmate.ai/api/v1/tasks?appointments=true&due_after=2026-10-01" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/tasks?appointments=true&due_after=2026-10-01', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/tasks",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"appointments": "true", "due_after": "2026-10-01"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "d4e5f6a7-5555-4000-8000-0000000000ee",
"object": "task",
"title": "Adjuster meeting — 1804 Cedar Ridge Dr",
"description": null,
"due_date": "2026-10-02T15:00:00",
"end_date": null,
"duration_minutes": 60,
"priority": "normal",
"completed": false,
"completed_at": null,
"is_appointment": true,
"appointment_type": "adjuster_meeting",
"outcome": null,
"customer_reminder": "sms",
"reminder_minutes": 60,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"contact_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"assigned_to_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"assigned_to_name": "Sam Carter",
"created_at": "2026-09-16T14:10:00.000Z",
"updated_at": "2026-09-16T14:10:00.000Z",
"url": "https://app.hailmate.ai/task/d4e5f6a7-5555-4000-8000-0000000000ee"
}
]
}Create a task or an appointment
/tasksSet appointment_type (inspection, adjuster_meeting, build_day) to make it an appointment; POST /appointments requires one.
Times are wall-clock. due_date is the local time your crew will read. If you send an offset (2026-10-02T15:00:00-05:00, which is what Zapier sends) the offset is dropped and 3:00 PM is kept — HailMate never shifts an appointment by a time zone.
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
Request bodyapplication/json
titlestringRequiredUp to 200 characters
descriptionstringdue_datestringA date or date-time. Any offset is dropped; the digits are kept.
end_datestringdateduration_minutesinteger15 to 480
prioritystringlownormalhighappointment_typestringinspectionadjuster_meetingbuild_daycustomer_reminderstringRemind the homeowner the day before.
nonesmsemailbothreminder_minutesinteger-105101530601201440job_idstringuuidcontact_idstringuuidassigned_tostringuuidcompletedboolean
curl -X POST https://app.hailmate.ai/api/v1/tasks \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"title": "Adjuster meeting — 1804 Cedar Ridge Dr",
"due_date": "2026-10-02T15:00:00",
"duration_minutes": 60,
"appointment_type": "adjuster_meeting",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"assigned_to": "5d0e1c2b-7777-4000-8000-0000000000ab"
}'import { randomUUID } from 'node:crypto';
const response = await fetch('https://app.hailmate.ai/api/v1/tasks', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({
title: 'Adjuster meeting — 1804 Cedar Ridge Dr',
due_date: '2026-10-02T15:00:00',
duration_minutes: 60,
appointment_type: 'adjuster_meeting',
job_id: 'f2b1a0c4-1111-4000-8000-0000000000aa',
assigned_to: '5d0e1c2b-7777-4000-8000-0000000000ab',
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import uuid
import requests
response = requests.post(
"https://app.hailmate.ai/api/v1/tasks",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"title": "Adjuster meeting — 1804 Cedar Ridge Dr",
"due_date": "2026-10-02T15:00:00",
"duration_minutes": 60,
"appointment_type": "adjuster_meeting",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"assigned_to": "5d0e1c2b-7777-4000-8000-0000000000ab",
},
)
response.raise_for_status()
data = response.json(){
"id": "d4e5f6a7-5555-4000-8000-0000000000ee",
"object": "task",
"title": "Adjuster meeting — 1804 Cedar Ridge Dr",
"description": null,
"due_date": "2026-10-02T15:00:00",
"end_date": null,
"duration_minutes": 60,
"priority": "normal",
"completed": false,
"completed_at": null,
"is_appointment": true,
"appointment_type": "adjuster_meeting",
"outcome": null,
"customer_reminder": "sms",
"reminder_minutes": 60,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"contact_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"assigned_to_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"assigned_to_name": "Sam Carter",
"created_at": "2026-09-16T14:10:00.000Z",
"updated_at": "2026-09-16T14:10:00.000Z",
"url": "https://app.hailmate.ai/task/d4e5f6a7-5555-4000-8000-0000000000ee"
}Get a task
/tasks/{id}curl https://app.hailmate.ai/api/v1/tasks/d4e5f6a7-5555-4000-8000-0000000000ee \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/tasks/d4e5f6a7-5555-4000-8000-0000000000ee', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/tasks/d4e5f6a7-5555-4000-8000-0000000000ee",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "d4e5f6a7-5555-4000-8000-0000000000ee",
"object": "task",
"title": "Adjuster meeting — 1804 Cedar Ridge Dr",
"description": null,
"due_date": "2026-10-02T15:00:00",
"end_date": null,
"duration_minutes": 60,
"priority": "normal",
"completed": false,
"completed_at": null,
"is_appointment": true,
"appointment_type": "adjuster_meeting",
"outcome": null,
"customer_reminder": "sms",
"reminder_minutes": 60,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"contact_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"assigned_to_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"assigned_to_name": "Sam Carter",
"created_at": "2026-09-16T14:10:00.000Z",
"updated_at": "2026-09-16T14:10:00.000Z",
"url": "https://app.hailmate.ai/task/d4e5f6a7-5555-4000-8000-0000000000ee"
}Update a task
/tasks/{id}Reschedule it, reassign it, or finish it with {"completed": true} (fires task.completed). null clears a field.
Path parameters
idstringuuidRequired
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
Request bodyapplication/json
titlestringdescriptionstring | nulldue_datestring | nullend_datestring | nulldateduration_minutesinteger | nullprioritystring | nullappointment_typestring | nulloutcomestring | nullcompletedno_showrescheduledcanceledcustomer_reminderstringreminder_minutesinteger | nulljob_idstring | nulluuidcontact_idstring | nulluuidassigned_tostring | nulluuidcompletedboolean
curl -X PATCH https://app.hailmate.ai/api/v1/tasks/d4e5f6a7-5555-4000-8000-0000000000ee \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"completed": true,
"outcome": "completed"
}'const response = await fetch('https://app.hailmate.ai/api/v1/tasks/d4e5f6a7-5555-4000-8000-0000000000ee', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
completed: true,
outcome: 'completed',
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.patch(
"https://app.hailmate.ai/api/v1/tasks/d4e5f6a7-5555-4000-8000-0000000000ee",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
json={
"completed": True,
"outcome": "completed",
},
)
response.raise_for_status()
data = response.json(){
"id": "d4e5f6a7-5555-4000-8000-0000000000ee",
"object": "task",
"title": "Adjuster meeting — 1804 Cedar Ridge Dr",
"description": null,
"due_date": "2026-10-02T15:00:00",
"end_date": null,
"duration_minutes": 60,
"priority": "normal",
"completed": false,
"completed_at": null,
"is_appointment": true,
"appointment_type": "adjuster_meeting",
"outcome": null,
"customer_reminder": "sms",
"reminder_minutes": 60,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"contact_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"assigned_to_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"assigned_to_name": "Sam Carter",
"created_at": "2026-09-16T14:10:00.000Z",
"updated_at": "2026-09-16T14:10:00.000Z",
"url": "https://app.hailmate.ai/task/d4e5f6a7-5555-4000-8000-0000000000ee"
}curl -X DELETE https://app.hailmate.ai/api/v1/tasks/d4e5f6a7-5555-4000-8000-0000000000ee \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/tasks/d4e5f6a7-5555-4000-8000-0000000000ee', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.delete(
"https://app.hailmate.ai/api/v1/tasks/d4e5f6a7-5555-4000-8000-0000000000ee",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"object": "job",
"deleted": true
}Endpoints
Estimates
Estimates and proposals — made from one of your templates or as your standard proposal, then finished and sent in HailMate.
List estimates
/estimatesQuery parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.updated_sincestringdate-timeOnly records changed at or after this ISO date or date-time. The way to sync only what moved.
created_afterstringdate-timeOnly records created at or after this ISO date or date-time.
created_beforestringdate-timeOnly records created before this ISO date or date-time.
statusstringdraftsentviewedsignedexpireddeclinedjob_idstringuuidestimate_numberstring
curl "https://app.hailmate.ai/api/v1/estimates?limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/estimates?limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/estimates",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "a41c9e20-2222-4000-8000-0000000000e1",
"object": "estimate",
"estimate_number": "EST-0214",
"title": "Roof replacement — 1804 Cedar Ridge Dr",
"document_type": "proposal",
"status": "sent",
"signature_status": "pending",
"total": 21900,
"package_totals": {
"good": 18450,
"better": 21900,
"best": 26400
},
"upgrades_total": 1250,
"selected_tier": null,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"valid_until": "2026-10-28T00:00:00.000Z",
"sent_at": "2026-09-28T15:10:00.000Z",
"first_viewed_at": "2026-09-28T18:42:00.000Z",
"last_viewed_at": "2026-09-28T19:05:00.000Z",
"view_count": 3,
"signed_at": null,
"signer_name": null,
"pdf_url": "https://files.example-cdn.com/estimates/EST-0214.pdf",
"signed_pdf_url": null,
"created_at": "2026-09-27T21:30:00.000Z",
"updated_at": "2026-09-28T19:05:00.000Z",
"url": "https://app.hailmate.ai/estimates/a41c9e20-2222-4000-8000-0000000000e1",
"view_url": "https://app.hailmate.ai/estimate/6f1d3b52-9c7e-4a10-8f3b-2d4e5a6b7c8d"
}
]
}Create an estimate
/estimatesMakes a draft estimate on a job, one of two ways — the same two the app offers:
- From one of your templates (
template_id, fromGET /estimate_templates): an exact copy, with its smart fields ({{customer_name}},{{property_address}}…) filled in from the job. - Your standard proposal: cover, introduction, Scope of Work, warranty, terms and signature. The Scope of Work is the lines you send, or HailMate's standard checklist when you send none.
price states one price for the whole job, which is what the homeowner sees as the total. The estimate is not sent — a person finishes and sends it in HailMate. Fires estimate.created.
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
Request bodyapplication/json
job_idstringuuidRequiredtemplate_idstringuuidOne of your templates. Leave out for your standard proposal.
titlestringpricenumberOne price for the whole job.
More than 0
valid_untilstringdateDefaults to your workspace's setting.
line_itemsarray of LineItemInputThe Scope of Work, without a template. Leave out for the standard checklist.
curl -X POST https://app.hailmate.ai/api/v1/estimates \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"template_id": "7a1c9e02-2222-4000-8000-0000000000c3",
"price": 14500
}'import { randomUUID } from 'node:crypto';
const response = await fetch('https://app.hailmate.ai/api/v1/estimates', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({
job_id: 'f2b1a0c4-1111-4000-8000-0000000000aa',
template_id: '7a1c9e02-2222-4000-8000-0000000000c3',
price: 14500,
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import uuid
import requests
response = requests.post(
"https://app.hailmate.ai/api/v1/estimates",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"template_id": "7a1c9e02-2222-4000-8000-0000000000c3",
"price": 14500,
},
)
response.raise_for_status()
data = response.json(){
"id": "a41c9e20-2222-4000-8000-0000000000e1",
"object": "estimate",
"estimate_number": "EST-0214",
"title": "Roof replacement — 1804 Cedar Ridge Dr",
"document_type": "proposal",
"status": "sent",
"signature_status": "pending",
"total": 21900,
"package_totals": {
"good": 18450,
"better": 21900,
"best": 26400
},
"upgrades_total": 1250,
"selected_tier": null,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"valid_until": "2026-10-28T00:00:00.000Z",
"sent_at": "2026-09-28T15:10:00.000Z",
"first_viewed_at": "2026-09-28T18:42:00.000Z",
"last_viewed_at": "2026-09-28T19:05:00.000Z",
"view_count": 3,
"signed_at": null,
"signer_name": null,
"pdf_url": "https://files.example-cdn.com/estimates/EST-0214.pdf",
"signed_pdf_url": null,
"created_at": "2026-09-27T21:30:00.000Z",
"updated_at": "2026-09-28T19:05:00.000Z",
"url": "https://app.hailmate.ai/estimates/a41c9e20-2222-4000-8000-0000000000e1",
"view_url": "https://app.hailmate.ai/estimate/6f1d3b52-9c7e-4a10-8f3b-2d4e5a6b7c8d"
}Get an estimate
/estimates/{id}curl https://app.hailmate.ai/api/v1/estimates/a41c9e20-2222-4000-8000-0000000000e1 \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/estimates/a41c9e20-2222-4000-8000-0000000000e1', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/estimates/a41c9e20-2222-4000-8000-0000000000e1",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "a41c9e20-2222-4000-8000-0000000000e1",
"object": "estimate",
"estimate_number": "EST-0214",
"title": "Roof replacement — 1804 Cedar Ridge Dr",
"document_type": "proposal",
"status": "sent",
"signature_status": "pending",
"total": 21900,
"package_totals": {
"good": 18450,
"better": 21900,
"best": 26400
},
"upgrades_total": 1250,
"selected_tier": null,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"valid_until": "2026-10-28T00:00:00.000Z",
"sent_at": "2026-09-28T15:10:00.000Z",
"first_viewed_at": "2026-09-28T18:42:00.000Z",
"last_viewed_at": "2026-09-28T19:05:00.000Z",
"view_count": 3,
"signed_at": null,
"signer_name": null,
"pdf_url": "https://files.example-cdn.com/estimates/EST-0214.pdf",
"signed_pdf_url": null,
"created_at": "2026-09-27T21:30:00.000Z",
"updated_at": "2026-09-28T19:05:00.000Z",
"url": "https://app.hailmate.ai/estimates/a41c9e20-2222-4000-8000-0000000000e1",
"view_url": "https://app.hailmate.ai/estimate/6f1d3b52-9c7e-4a10-8f3b-2d4e5a6b7c8d"
}Update an estimate
/estimates/{id}Changes what sits outside the document: its title, how long it is valid, and one stated price (null takes the price off). The sections and lines are edited in HailMate. Refused with 409 once the estimate is out for signature or signed.
Path parameters
idstringuuidRequired
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
Request bodyapplication/json
titlestring | nullpricenumber | nullnulltakes the stated price off.More than 0
valid_untilstring | nulldate
Returns
200The estimate, as it is now. An Estimate object.
- 400
invalid_requestSomething in the request was wrong;fieldnames what. - 404
not_foundNo such record in this workspace. - 409
conflictAn Idempotency-Key clash — reused for a different request, or the first is still running.
Any request can also answer 401, 402, 429 or 500 — errors.
curl -X PATCH https://app.hailmate.ai/api/v1/estimates/a41c9e20-2222-4000-8000-0000000000e1 \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"price": 15250,
"valid_until": "2026-12-01"
}'const response = await fetch('https://app.hailmate.ai/api/v1/estimates/a41c9e20-2222-4000-8000-0000000000e1', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
price: 15250,
valid_until: '2026-12-01',
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.patch(
"https://app.hailmate.ai/api/v1/estimates/a41c9e20-2222-4000-8000-0000000000e1",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
json={
"price": 15250,
"valid_until": "2026-12-01",
},
)
response.raise_for_status()
data = response.json(){
"id": "a41c9e20-2222-4000-8000-0000000000e1",
"object": "estimate",
"estimate_number": "EST-0214",
"title": "Roof replacement — 1804 Cedar Ridge Dr",
"document_type": "proposal",
"status": "sent",
"signature_status": "pending",
"total": 21900,
"package_totals": {
"good": 18450,
"better": 21900,
"best": 26400
},
"upgrades_total": 1250,
"selected_tier": null,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"valid_until": "2026-10-28T00:00:00.000Z",
"sent_at": "2026-09-28T15:10:00.000Z",
"first_viewed_at": "2026-09-28T18:42:00.000Z",
"last_viewed_at": "2026-09-28T19:05:00.000Z",
"view_count": 3,
"signed_at": null,
"signer_name": null,
"pdf_url": "https://files.example-cdn.com/estimates/EST-0214.pdf",
"signed_pdf_url": null,
"created_at": "2026-09-27T21:30:00.000Z",
"updated_at": "2026-09-28T19:05:00.000Z",
"url": "https://app.hailmate.ai/estimates/a41c9e20-2222-4000-8000-0000000000e1",
"view_url": "https://app.hailmate.ai/estimate/6f1d3b52-9c7e-4a10-8f3b-2d4e5a6b7c8d"
}List estimate templates
/estimate_templatesThe workspace's own templates, most recently changed first — what a "which template?" dropdown lists.
Query parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50
Returns
200The templates. A list of EstimateTemplate objects in data.
- 401
unauthorizedThe key is missing, wrong or revoked.
Any request can also answer 401, 402, 429 or 500 — errors.
curl "https://app.hailmate.ai/api/v1/estimate_templates?limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/estimate_templates?limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/estimate_templates",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"data": [
{
"id": "7a1c9e02-2222-4000-8000-0000000000c3",
"object": "estimate_template",
"name": "Insurance roof replacement",
"template_number": "TPL-0003",
"created_at": "2026-08-30T15:00:00.000Z",
"updated_at": "2026-09-20T18:30:00.000Z"
}
],
"has_more": false,
"next_cursor": null
}Endpoints
Invoices
Draft invoices, totalled exactly as HailMate totals them; mark them sent, or void them.
List invoices
/invoicesQuery parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.updated_sincestringdate-timeOnly records changed at or after this ISO date or date-time. The way to sync only what moved.
created_afterstringdate-timeOnly records created at or after this ISO date or date-time.
created_beforestringdate-timeOnly records created before this ISO date or date-time.
statusstringdraftsentviewedpartially_paidpaidoverduevoidbad_debtjob_idstringuuidcontact_idstringuuidinvoice_numberstring
curl "https://app.hailmate.ai/api/v1/invoices?limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/invoices?limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/invoices",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "c90d7b31-3333-4000-8000-0000000000f2",
"object": "invoice",
"invoice_number": "INV-0311",
"title": "Deductible",
"status": "sent",
"purpose": "deductible",
"subtotal": 2500,
"discount_amount": 0,
"tax_amount": 0,
"late_fee_amount": 0,
"total_amount": 2500,
"amount_paid": 0,
"balance_due": 2500,
"part_number": null,
"part_count": null,
"issue_date": "2026-09-28",
"due_date": "2026-10-12",
"sent_at": "2026-09-28T16:00:00.000Z",
"first_viewed_at": null,
"paid_at": null,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"contact_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"estimate_id": null,
"pdf_url": "https://files.example-cdn.com/invoices/INV-0311.pdf",
"created_at": "2026-09-28T15:55:00.000Z",
"updated_at": "2026-09-28T16:00:00.000Z",
"url": "https://app.hailmate.ai/jobs/invoice/c90d7b31-3333-4000-8000-0000000000f2",
"payment_url": "https://app.hailmate.ai/invoice/0b7e2c19-5d4a-4f3e-9a1b-8c6d2e4f1a37"
}
]
}Create an invoice
/invoicesMakes a draft invoice for a job (job_id) or a contact (contact_id), with at least one line. HailMate numbers it (INV-0312) and totals it with the same calculator the app uses: lines, minus any discount, plus tax on the taxable lines. A negative price is a credit. purpose fills the matching figure on the job — a deductible invoice sets the job's deductible — exactly as in the app.
A draft is not sent. Send it from HailMate, or call mark_sent when it went out another way. Fires invoice.created.
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
Request bodyapplication/json
job_idstringuuidcontact_idstringuuidWho it bills.
estimate_idstringuuidtitlestringpurposestringclaim_scopedeductiblesupplementdepreciationcontractotherissue_datestringdateDefaults to today.
due_datestringdatetax_ratenumberA percent
0 to 100
discount_typestringamountpercentdiscount_valuenumberAt least 0
notesstringtermsstringallow_partial_paymentsboolean
curl -X POST https://app.hailmate.ai/api/v1/invoices \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"title": "Deductible",
"purpose": "deductible",
"due_date": "2026-10-12",
"line_items": [
{
"name": "Insurance deductible",
"quantity": 1,
"unit_price": 2500
}
]
}'import { randomUUID } from 'node:crypto';
const response = await fetch('https://app.hailmate.ai/api/v1/invoices', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({
job_id: 'f2b1a0c4-1111-4000-8000-0000000000aa',
title: 'Deductible',
purpose: 'deductible',
due_date: '2026-10-12',
line_items: [
{
name: 'Insurance deductible',
quantity: 1,
unit_price: 2500,
},
],
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import uuid
import requests
response = requests.post(
"https://app.hailmate.ai/api/v1/invoices",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"title": "Deductible",
"purpose": "deductible",
"due_date": "2026-10-12",
"line_items": [
{
"name": "Insurance deductible",
"quantity": 1,
"unit_price": 2500,
},
],
},
)
response.raise_for_status()
data = response.json(){
"id": "c90d7b31-3333-4000-8000-0000000000f2",
"object": "invoice",
"invoice_number": "INV-0311",
"title": "Deductible",
"status": "sent",
"purpose": "deductible",
"subtotal": 2500,
"discount_amount": 0,
"tax_amount": 0,
"late_fee_amount": 0,
"total_amount": 2500,
"amount_paid": 0,
"balance_due": 2500,
"part_number": null,
"part_count": null,
"issue_date": "2026-09-28",
"due_date": "2026-10-12",
"sent_at": "2026-09-28T16:00:00.000Z",
"first_viewed_at": null,
"paid_at": null,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"contact_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"estimate_id": null,
"pdf_url": "https://files.example-cdn.com/invoices/INV-0311.pdf",
"created_at": "2026-09-28T15:55:00.000Z",
"updated_at": "2026-09-28T16:00:00.000Z",
"url": "https://app.hailmate.ai/jobs/invoice/c90d7b31-3333-4000-8000-0000000000f2",
"payment_url": "https://app.hailmate.ai/invoice/0b7e2c19-5d4a-4f3e-9a1b-8c6d2e4f1a37"
}Get an invoice
/invoices/{id}curl https://app.hailmate.ai/api/v1/invoices/c90d7b31-3333-4000-8000-0000000000f2 \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/invoices/c90d7b31-3333-4000-8000-0000000000f2', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/invoices/c90d7b31-3333-4000-8000-0000000000f2",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "c90d7b31-3333-4000-8000-0000000000f2",
"object": "invoice",
"invoice_number": "INV-0311",
"title": "Deductible",
"status": "sent",
"purpose": "deductible",
"subtotal": 2500,
"discount_amount": 0,
"tax_amount": 0,
"late_fee_amount": 0,
"total_amount": 2500,
"amount_paid": 0,
"balance_due": 2500,
"part_number": null,
"part_count": null,
"issue_date": "2026-09-28",
"due_date": "2026-10-12",
"sent_at": "2026-09-28T16:00:00.000Z",
"first_viewed_at": null,
"paid_at": null,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"contact_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"estimate_id": null,
"pdf_url": "https://files.example-cdn.com/invoices/INV-0311.pdf",
"created_at": "2026-09-28T15:55:00.000Z",
"updated_at": "2026-09-28T16:00:00.000Z",
"url": "https://app.hailmate.ai/jobs/invoice/c90d7b31-3333-4000-8000-0000000000f2",
"payment_url": "https://app.hailmate.ai/invoice/0b7e2c19-5d4a-4f3e-9a1b-8c6d2e4f1a37"
}Update a draft invoice
/invoices/{id}Only a draft can change — once an invoice is sent the homeowner holds a copy, so a sent one is voided and replaced instead (409). line_items, when sent, replace every line; the totals are worked out again either way.
Path parameters
idstringuuidRequired
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
Request bodyapplication/json
contact_idstring | nulluuidestimate_idstring | nulluuidtitlestring | nullpurposestring | nullclaim_scopedeductiblesupplementdepreciationcontractotherissue_datestring | nulldatedue_datestring | nulldateline_itemsarray of LineItemInputReplaces every line.
tax_ratenumber0 to 100
discount_typestringamountpercentdiscount_valuenumber | null0ornulltakes the discount off.At least 0
notesstring | nulltermsstring | nullallow_partial_paymentsboolean
Returns
200The invoice, as it is now. An Invoice object.
- 400
invalid_requestSomething in the request was wrong;fieldnames what. - 404
not_foundNo such record in this workspace. - 409
conflictAn Idempotency-Key clash — reused for a different request, or the first is still running.
Any request can also answer 401, 402, 429 or 500 — errors.
curl -X PATCH https://app.hailmate.ai/api/v1/invoices/c90d7b31-3333-4000-8000-0000000000f2 \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"due_date": "2026-10-20",
"tax_rate": 8.25
}'const response = await fetch('https://app.hailmate.ai/api/v1/invoices/c90d7b31-3333-4000-8000-0000000000f2', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
due_date: '2026-10-20',
tax_rate: 8.25,
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.patch(
"https://app.hailmate.ai/api/v1/invoices/c90d7b31-3333-4000-8000-0000000000f2",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
json={
"due_date": "2026-10-20",
"tax_rate": 8.25,
},
)
response.raise_for_status()
data = response.json(){
"id": "c90d7b31-3333-4000-8000-0000000000f2",
"object": "invoice",
"invoice_number": "INV-0311",
"title": "Deductible",
"status": "sent",
"purpose": "deductible",
"subtotal": 2500,
"discount_amount": 0,
"tax_amount": 0,
"late_fee_amount": 0,
"total_amount": 2500,
"amount_paid": 0,
"balance_due": 2500,
"part_number": null,
"part_count": null,
"issue_date": "2026-09-28",
"due_date": "2026-10-12",
"sent_at": "2026-09-28T16:00:00.000Z",
"first_viewed_at": null,
"paid_at": null,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"contact_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"estimate_id": null,
"pdf_url": "https://files.example-cdn.com/invoices/INV-0311.pdf",
"created_at": "2026-09-28T15:55:00.000Z",
"updated_at": "2026-09-28T16:00:00.000Z",
"url": "https://app.hailmate.ai/jobs/invoice/c90d7b31-3333-4000-8000-0000000000f2",
"payment_url": "https://app.hailmate.ai/invoice/0b7e2c19-5d4a-4f3e-9a1b-8c6d2e4f1a37"
}Delete a draft invoice
/invoices/{id}Only a draft. A sent invoice is voided instead, so its history stays on the job.
Path parameters
idstringuuidRequired
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
curl -X DELETE https://app.hailmate.ai/api/v1/invoices/c90d7b31-3333-4000-8000-0000000000f2 \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/invoices/c90d7b31-3333-4000-8000-0000000000f2', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.delete(
"https://app.hailmate.ai/api/v1/invoices/c90d7b31-3333-4000-8000-0000000000f2",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "c90d7b31-3333-4000-8000-0000000000f2",
"object": "invoice",
"deleted": true
}Mark an invoice sent
/invoices/{id}/mark_sentRecords that the invoice went out some other way. A draft becomes sent, which turns on the homeowner's pay page (payment_url); an invoice already out keeps its status. Refused for a void invoice.
Path parameters
idstringuuidRequired
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
curl -X POST https://app.hailmate.ai/api/v1/invoices/c90d7b31-3333-4000-8000-0000000000f2/mark_sent \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { randomUUID } from 'node:crypto';
const response = await fetch('https://app.hailmate.ai/api/v1/invoices/c90d7b31-3333-4000-8000-0000000000f2/mark_sent', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Idempotency-Key': randomUUID(),
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import uuid
import requests
response = requests.post(
"https://app.hailmate.ai/api/v1/invoices/c90d7b31-3333-4000-8000-0000000000f2/mark_sent",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
)
response.raise_for_status()
data = response.json(){
"id": "c90d7b31-3333-4000-8000-0000000000f2",
"object": "invoice",
"invoice_number": "INV-0311",
"title": "Deductible",
"status": "sent",
"purpose": "deductible",
"subtotal": 2500,
"discount_amount": 0,
"tax_amount": 0,
"late_fee_amount": 0,
"total_amount": 2500,
"amount_paid": 0,
"balance_due": 2500,
"part_number": null,
"part_count": null,
"issue_date": "2026-09-28",
"due_date": "2026-10-12",
"sent_at": "2026-09-28T16:00:00.000Z",
"first_viewed_at": null,
"paid_at": null,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"contact_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"estimate_id": null,
"pdf_url": "https://files.example-cdn.com/invoices/INV-0311.pdf",
"created_at": "2026-09-28T15:55:00.000Z",
"updated_at": "2026-09-28T16:00:00.000Z",
"url": "https://app.hailmate.ai/jobs/invoice/c90d7b31-3333-4000-8000-0000000000f2",
"payment_url": "https://app.hailmate.ai/invoice/0b7e2c19-5d4a-4f3e-9a1b-8c6d2e4f1a37"
}Void an invoice
/invoices/{id}/voidVoids the invoice. Voiding one that is already void answers with it, unchanged.
Path parameters
idstringuuidRequired
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
curl -X POST https://app.hailmate.ai/api/v1/invoices/c90d7b31-3333-4000-8000-0000000000f2/void \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { randomUUID } from 'node:crypto';
const response = await fetch('https://app.hailmate.ai/api/v1/invoices/c90d7b31-3333-4000-8000-0000000000f2/void', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Idempotency-Key': randomUUID(),
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import uuid
import requests
response = requests.post(
"https://app.hailmate.ai/api/v1/invoices/c90d7b31-3333-4000-8000-0000000000f2/void",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
)
response.raise_for_status()
data = response.json(){
"id": "c90d7b31-3333-4000-8000-0000000000f2",
"object": "invoice",
"invoice_number": "INV-0311",
"title": "Deductible",
"status": "sent",
"purpose": "deductible",
"subtotal": 2500,
"discount_amount": 0,
"tax_amount": 0,
"late_fee_amount": 0,
"total_amount": 2500,
"amount_paid": 0,
"balance_due": 2500,
"part_number": null,
"part_count": null,
"issue_date": "2026-09-28",
"due_date": "2026-10-12",
"sent_at": "2026-09-28T16:00:00.000Z",
"first_viewed_at": null,
"paid_at": null,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"contact_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"estimate_id": null,
"pdf_url": "https://files.example-cdn.com/invoices/INV-0311.pdf",
"created_at": "2026-09-28T15:55:00.000Z",
"updated_at": "2026-09-28T16:00:00.000Z",
"url": "https://app.hailmate.ai/jobs/invoice/c90d7b31-3333-4000-8000-0000000000f2",
"payment_url": "https://app.hailmate.ai/invoice/0b7e2c19-5d4a-4f3e-9a1b-8c6d2e4f1a37"
}Endpoints
Payments
Money received against a job — carrier cheques, deductibles, card payments.
List payments
/paymentsA refund is its own row with a negative amount, so summing amount gives the net.
Query parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.updated_sincestringdate-timeOnly records changed at or after this ISO date or date-time. The way to sync only what moved.
created_afterstringdate-timeOnly records created at or after this ISO date or date-time.
created_beforestringdate-timeOnly records created before this ISO date or date-time.
job_idstringuuidinvoice_idstringuuidpayment_typestringacvdeductiblesupplementdepreciationretailother
curl "https://app.hailmate.ai/api/v1/payments?job_id=f2b1a0c4-1111-4000-8000-0000000000aa" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/payments?job_id=f2b1a0c4-1111-4000-8000-0000000000aa', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/payments",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "9fa3e1d2-5555-4000-8000-0000000000c3",
"object": "payment",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"invoice_id": null,
"amount": 13164,
"payment_type": "acv",
"payer_type": "insurance",
"payment_method": "check",
"status": "received",
"check_number": "4471",
"date_received": "2026-09-26",
"date_deposited": null,
"notes": "First ACV check from Example Mutual",
"is_refund": false,
"refund_of_payment_id": null,
"online": false,
"created_at": "2026-09-26T20:14:00.000Z",
"updated_at": "2026-09-26T20:14:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa"
}
]
}Record a payment
/paymentsRecords money that arrived — a cheque logged in your accounting app, a deposit from a bank feed. The job's money and, when you name one, the invoice's balance and status update exactly as they do for a payment logged in HailMate.
The invoice it pays is never guessed. Send invoice_id to apply it to an invoice on the same job; leave it out and the payment sits on the job. Fires payment.received.
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
Request bodyapplication/json
job_idstringuuidRequiredamountnumberRequiredMore than 0
invoice_idstringuuidAn invoice ON THIS JOB to apply it to. Never guessed.
payment_typestringacvdeductiblesupplementdepreciationretailotherDefault
otherpayer_typestringinsurancehomeownermortgage_companyotherDefault
homeownerpayment_methodstringcheckcashcardachfinancingotherDefault
checkstatusstringexpectedreceivedsent_to_mortgageendorseddepositedDefault
receivedcheck_numberstringdate_receivedstringdateDefaults to today (US Central).
date_depositedstringdatenotesstring
curl -X POST https://app.hailmate.ai/api/v1/payments \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"amount": 13164.2,
"payment_type": "acv",
"payer_type": "insurance",
"payment_method": "check",
"check_number": "004471",
"date_received": "2026-09-28"
}'import { randomUUID } from 'node:crypto';
const response = await fetch('https://app.hailmate.ai/api/v1/payments', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({
job_id: 'f2b1a0c4-1111-4000-8000-0000000000aa',
amount: 13164.2,
payment_type: 'acv',
payer_type: 'insurance',
payment_method: 'check',
check_number: '004471',
date_received: '2026-09-28',
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import uuid
import requests
response = requests.post(
"https://app.hailmate.ai/api/v1/payments",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"amount": 13164.2,
"payment_type": "acv",
"payer_type": "insurance",
"payment_method": "check",
"check_number": "004471",
"date_received": "2026-09-28",
},
)
response.raise_for_status()
data = response.json(){
"id": "9fa3e1d2-5555-4000-8000-0000000000c3",
"object": "payment",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"invoice_id": null,
"amount": 13164,
"payment_type": "acv",
"payer_type": "insurance",
"payment_method": "check",
"status": "received",
"check_number": "4471",
"date_received": "2026-09-26",
"date_deposited": null,
"notes": "First ACV check from Example Mutual",
"is_refund": false,
"refund_of_payment_id": null,
"online": false,
"created_at": "2026-09-26T20:14:00.000Z",
"updated_at": "2026-09-26T20:14:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa"
}Get a payment
/payments/{id}curl https://app.hailmate.ai/api/v1/payments/9fa3e1d2-5555-4000-8000-0000000000c3 \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/payments/9fa3e1d2-5555-4000-8000-0000000000c3', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/payments/9fa3e1d2-5555-4000-8000-0000000000c3",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "9fa3e1d2-5555-4000-8000-0000000000c3",
"object": "payment",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"invoice_id": null,
"amount": 13164,
"payment_type": "acv",
"payer_type": "insurance",
"payment_method": "check",
"status": "received",
"check_number": "4471",
"date_received": "2026-09-26",
"date_deposited": null,
"notes": "First ACV check from Example Mutual",
"is_refund": false,
"refund_of_payment_id": null,
"online": false,
"created_at": "2026-09-26T20:14:00.000Z",
"updated_at": "2026-09-26T20:14:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa"
}Endpoints
Notes
Notes on a job's timeline.
List notes
/notesQuery parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.updated_sincestringdate-timeOnly records changed at or after this ISO date or date-time. The way to sync only what moved.
job_idstringuuid
curl "https://app.hailmate.ai/api/v1/notes?limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/notes?limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/notes",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "3c8b1a07-6666-4000-8000-0000000000b4",
"object": "note",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"content": "Adjuster meeting moved to Thursday 10am. Homeowner will be home.",
"author_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"author_name": "Sam Carter",
"created_at": "2026-09-27T14:20:00.000Z",
"updated_at": "2026-09-27T14:20:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa"
}
]
}Add a note to a job
/notesHeaders
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
Request bodyapplication/json
job_idstringuuidRequiredcontentstringRequiredUp to 10000 characters
curl -X POST https://app.hailmate.ai/api/v1/notes \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"content": "Adjuster confirmed for Thursday at 3."
}'import { randomUUID } from 'node:crypto';
const response = await fetch('https://app.hailmate.ai/api/v1/notes', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({
job_id: 'f2b1a0c4-1111-4000-8000-0000000000aa',
content: 'Adjuster confirmed for Thursday at 3.',
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import uuid
import requests
response = requests.post(
"https://app.hailmate.ai/api/v1/notes",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"content": "Adjuster confirmed for Thursday at 3.",
},
)
response.raise_for_status()
data = response.json(){
"id": "3c8b1a07-6666-4000-8000-0000000000b4",
"object": "note",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"content": "Adjuster meeting moved to Thursday 10am. Homeowner will be home.",
"author_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"author_name": "Sam Carter",
"created_at": "2026-09-27T14:20:00.000Z",
"updated_at": "2026-09-27T14:20:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa"
}Get a note
/notes/{id}curl https://app.hailmate.ai/api/v1/notes/3c8b1a07-6666-4000-8000-0000000000b4 \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/notes/3c8b1a07-6666-4000-8000-0000000000b4', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/notes/3c8b1a07-6666-4000-8000-0000000000b4",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "3c8b1a07-6666-4000-8000-0000000000b4",
"object": "note",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"content": "Adjuster meeting moved to Thursday 10am. Homeowner will be home.",
"author_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"author_name": "Sam Carter",
"created_at": "2026-09-27T14:20:00.000Z",
"updated_at": "2026-09-27T14:20:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa"
}Endpoints
Files
Photos and documents on a job.
List photos and documents
/filesQuery parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.created_afterstringdate-timeOnly records created at or after this ISO date or date-time.
created_beforestringdate-timeOnly records created before this ISO date or date-time.
job_idstringuuidcategorystringphotodocumentscopeestimatecontractother
curl "https://app.hailmate.ai/api/v1/files?limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/files?limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/files",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "7e2d9c40-8888-4000-8000-0000000000a5",
"object": "file",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"name": "north-slope-hits.jpg",
"category": "photo",
"mime_type": "image/jpeg",
"is_photo": true,
"is_video": false,
"size_bytes": 1532410,
"description": "Hail hits on the north slope, chalked",
"tags": [
"damage",
"north slope"
],
"latitude": 33.0726,
"longitude": -96.7512,
"taken_at": "2026-09-16T15:12:08.000Z",
"uploaded_by_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"uploaded_by_name": "Sam Carter",
"download_url": "https://files.example-cdn.com/jobs/f2b1a0c4/north-slope-hits.jpg",
"created_at": "2026-09-16T15:12:40.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa"
}
]
}Upload a photo or document to a job
/filesTwo ways in:
- multipart/form-data with a
filefield and ajob_idfield (plus optionalcategory,description,file_name). - JSON with a public
file_urlHailMate downloads for you.
Up to 25 MB. A JPEG, PNG, WebP, GIF or MP4 is filed as a photo; anything else (a PDF, a HEIC, a spreadsheet) as a document, so it never shows as a broken tile. Fires file.created.
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
Request bodyapplication/json
job_idstringuuidRequiredfile_urlstringuriRequiredA public https address. Redirects are followed (up to 3).
categorystringphotodocumentscopeestimatecontractotherdescriptionstringfile_namestring
Request bodymultipart/form-data
Or send the file itself as a form upload:
filestringbinaryRequiredjob_idstringuuidRequiredcategorystringphotodocumentscopeestimatecontractotherdescriptionstringfile_namestring
curl -X POST https://app.hailmate.ai/api/v1/files \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"file_url": "https://example.com/claim/scope-of-loss.pdf",
"category": "scope"
}'import { randomUUID } from 'node:crypto';
const response = await fetch('https://app.hailmate.ai/api/v1/files', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({
job_id: 'f2b1a0c4-1111-4000-8000-0000000000aa',
file_url: 'https://example.com/claim/scope-of-loss.pdf',
category: 'scope',
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import uuid
import requests
response = requests.post(
"https://app.hailmate.ai/api/v1/files",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"file_url": "https://example.com/claim/scope-of-loss.pdf",
"category": "scope",
},
)
response.raise_for_status()
data = response.json()curl -X POST https://app.hailmate.ai/api/v1/files \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-F "file=@roof-front.jpg" \
-F "job_id=f2b1a0c4-1111-4000-8000-0000000000aa" \
-F "description=Front slope, hail hits circled"{
"id": "7e2d9c40-8888-4000-8000-0000000000a5",
"object": "file",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"name": "north-slope-hits.jpg",
"category": "photo",
"mime_type": "image/jpeg",
"is_photo": true,
"is_video": false,
"size_bytes": 1532410,
"description": "Hail hits on the north slope, chalked",
"tags": [
"damage",
"north slope"
],
"latitude": 33.0726,
"longitude": -96.7512,
"taken_at": "2026-09-16T15:12:08.000Z",
"uploaded_by_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"uploaded_by_name": "Sam Carter",
"download_url": "https://files.example-cdn.com/jobs/f2b1a0c4/north-slope-hits.jpg",
"created_at": "2026-09-16T15:12:40.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa"
}Get a file
/files/{id}curl https://app.hailmate.ai/api/v1/files/7e2d9c40-8888-4000-8000-0000000000a5 \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/files/7e2d9c40-8888-4000-8000-0000000000a5', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/files/7e2d9c40-8888-4000-8000-0000000000a5",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "7e2d9c40-8888-4000-8000-0000000000a5",
"object": "file",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"name": "north-slope-hits.jpg",
"category": "photo",
"mime_type": "image/jpeg",
"is_photo": true,
"is_video": false,
"size_bytes": 1532410,
"description": "Hail hits on the north slope, chalked",
"tags": [
"damage",
"north slope"
],
"latitude": 33.0726,
"longitude": -96.7512,
"taken_at": "2026-09-16T15:12:08.000Z",
"uploaded_by_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"uploaded_by_name": "Sam Carter",
"download_url": "https://files.example-cdn.com/jobs/f2b1a0c4/north-slope-hits.jpg",
"created_at": "2026-09-16T15:12:40.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa"
}Endpoints
Canvassing
Door knocks — pins on the canvassing map with a knock result.
List canvassing pins
/pinsEvery door a rep dropped a pin on, newest first.
Query parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.updated_sincestringdate-timeOnly records changed at or after this ISO date or date-time. The way to sync only what moved.
created_afterstringdate-timeOnly records created at or after this ISO date or date-time.
created_beforestringdate-timeOnly records created before this ISO date or date-time.
knock_resultstringinterestednot_interestedcontactedno_answerdoor_hangerfollow_upappointment_scheduleddont_knockrenterno_damagecash_quotelostpin_typestringknockinspectionjobjob_idstringuuidcreated_bystringuuidThe rep who dropped the pin.
curl "https://app.hailmate.ai/api/v1/pins?limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/pins?limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/pins",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "2b6f4e18-9999-4000-8000-0000000000d6",
"object": "pin",
"pin_type": "knock",
"knock_result": "interested",
"address": "1812 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"latitude": 33.0731,
"longitude": -96.7519,
"homeowner_name": "Chris Lane",
"homeowner_first_name": "Chris",
"homeowner_last_name": "Lane",
"phone": "(972) 555-0193",
"email": null,
"notes": "Wants an inspection Saturday morning",
"job_id": null,
"created_by_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"created_by_name": "Sam Carter",
"created_at": "2026-09-27T17:45:00.000Z",
"updated_at": "2026-09-27T17:45:00.000Z",
"map_url": "https://www.google.com/maps?q=33.0731,-96.7519",
"url": "https://app.hailmate.ai/canvassing"
}
]
}Log a door knock
/pinsDrops a pin on the canvassing map. Send latitude and longitude, or an address and HailMate places it. It is credited to the teammate in created_by_id, else to whoever connected the integration. Fires pin.created.
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
Request bodyapplication/json
latitudenumber-90 to 90
longitudenumber-180 to 180
addressstringcitystringstatestringA code or a name — "TX" or "Texas".
postal_codestringknock_resultstringinterestednot_interestedcontactedno_answerdoor_hangerfollow_upappointment_scheduleddont_knockrenterno_damagecash_quotelostpin_typestringknockinspectionDefault
knockhomeowner_namestringSplit into first and last for you.
homeowner_first_namestringhomeowner_last_namestringphonestringemailstringemailnotesstringcreated_by_idstringuuidThe teammate who knocked. Defaults to whoever connected the integration.
curl -X POST https://app.hailmate.ai/api/v1/pins \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"knock_result": "interested",
"homeowner_name": "Dana Reed"
}'import { randomUUID } from 'node:crypto';
const response = await fetch('https://app.hailmate.ai/api/v1/pins', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({
address: '1804 Cedar Ridge Dr',
city: 'Plano',
state: 'TX',
postal_code: '75024',
knock_result: 'interested',
homeowner_name: 'Dana Reed',
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import uuid
import requests
response = requests.post(
"https://app.hailmate.ai/api/v1/pins",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"knock_result": "interested",
"homeowner_name": "Dana Reed",
},
)
response.raise_for_status()
data = response.json(){
"id": "2b6f4e18-9999-4000-8000-0000000000d6",
"object": "pin",
"pin_type": "knock",
"knock_result": "interested",
"address": "1812 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"latitude": 33.0731,
"longitude": -96.7519,
"homeowner_name": "Chris Lane",
"homeowner_first_name": "Chris",
"homeowner_last_name": "Lane",
"phone": "(972) 555-0193",
"email": null,
"notes": "Wants an inspection Saturday morning",
"job_id": null,
"created_by_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"created_by_name": "Sam Carter",
"created_at": "2026-09-27T17:45:00.000Z",
"updated_at": "2026-09-27T17:45:00.000Z",
"map_url": "https://www.google.com/maps?q=33.0731,-96.7519",
"url": "https://app.hailmate.ai/canvassing"
}Get a pin
/pins/{id}curl https://app.hailmate.ai/api/v1/pins/2b6f4e18-9999-4000-8000-0000000000d6 \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/pins/2b6f4e18-9999-4000-8000-0000000000d6', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/pins/2b6f4e18-9999-4000-8000-0000000000d6",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "2b6f4e18-9999-4000-8000-0000000000d6",
"object": "pin",
"pin_type": "knock",
"knock_result": "interested",
"address": "1812 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"latitude": 33.0731,
"longitude": -96.7519,
"homeowner_name": "Chris Lane",
"homeowner_first_name": "Chris",
"homeowner_last_name": "Lane",
"phone": "(972) 555-0193",
"email": null,
"notes": "Wants an inspection Saturday morning",
"job_id": null,
"created_by_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"created_by_name": "Sam Carter",
"created_at": "2026-09-27T17:45:00.000Z",
"updated_at": "2026-09-27T17:45:00.000Z",
"map_url": "https://www.google.com/maps?q=33.0731,-96.7519",
"url": "https://app.hailmate.ai/canvassing"
}Update a door knock
/pins/{id}Most often its knock result. The rep who logged it never changes. Fires pin.result_changed when the result moves.
Path parameters
idstringuuidRequired
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
Request bodyapplication/json
knock_resultstring | nullinterestednot_interestedcontactedno_answerdoor_hangerfollow_upappointment_scheduleddont_knockrenterno_damagecash_quotelostpin_typestringknockinspectionlatitudenumberlongitudenumberaddressstring | nullcitystring | nullstatestring | nullpostal_codestring | nullhomeowner_namestringhomeowner_first_namestring | nullhomeowner_last_namestring | nullphonestring | nullemailstring | nullemailnotesstring | null
curl -X PATCH https://app.hailmate.ai/api/v1/pins/2b6f4e18-9999-4000-8000-0000000000d6 \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"knock_result": "appointment_scheduled"
}'const response = await fetch('https://app.hailmate.ai/api/v1/pins/2b6f4e18-9999-4000-8000-0000000000d6', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
knock_result: 'appointment_scheduled',
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.patch(
"https://app.hailmate.ai/api/v1/pins/2b6f4e18-9999-4000-8000-0000000000d6",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
json={
"knock_result": "appointment_scheduled",
},
)
response.raise_for_status()
data = response.json(){
"id": "2b6f4e18-9999-4000-8000-0000000000d6",
"object": "pin",
"pin_type": "knock",
"knock_result": "interested",
"address": "1812 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"latitude": 33.0731,
"longitude": -96.7519,
"homeowner_name": "Chris Lane",
"homeowner_first_name": "Chris",
"homeowner_last_name": "Lane",
"phone": "(972) 555-0193",
"email": null,
"notes": "Wants an inspection Saturday morning",
"job_id": null,
"created_by_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"created_by_name": "Sam Carter",
"created_at": "2026-09-27T17:45:00.000Z",
"updated_at": "2026-09-27T17:45:00.000Z",
"map_url": "https://www.google.com/maps?q=33.0731,-96.7519",
"url": "https://app.hailmate.ai/canvassing"
}Delete a door knock
/pins/{id}Path parameters
idstringuuidRequired
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
curl -X DELETE https://app.hailmate.ai/api/v1/pins/2b6f4e18-9999-4000-8000-0000000000d6 \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/pins/2b6f4e18-9999-4000-8000-0000000000d6', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.delete(
"https://app.hailmate.ai/api/v1/pins/2b6f4e18-9999-4000-8000-0000000000d6",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "0e4c2a19-8888-4000-8000-0000000000c1",
"object": "pin",
"deleted": true
}Endpoints
Storm Lists
Every property under hail of a chosen size, with the owner of record (read only).
List storm lists
/storm_listsRead only — building a list spends data credits and happens in HailMate. source: auto is a nightly build off the hail data; manual is one somebody made.
Query parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.updated_sincestringdate-timeOnly records changed at or after this ISO date or date-time. The way to sync only what moved.
statusstringbuildingreadypartialfailed
curl "https://app.hailmate.ai/api/v1/storm_lists?limit=25" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/storm_lists?limit=25', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/storm_lists",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"limit": "25"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "e5a1c7d9-aaaa-4000-8000-0000000000e7",
"object": "storm_list",
"name": "Plano 1.25″+ — Sep 12",
"status": "ready",
"source": "manual",
"window_from": "2026-09-12",
"window_to": "2026-09-12",
"min_size_in": 1.25,
"area_kind": "drawn",
"territory_id": null,
"clipped_area_sq_mi": 4.2,
"property_cap": 1000,
"property_count": 412,
"skipped_duplicates": 18,
"dedupe_days": 90,
"credits_spent": 412,
"total_available": 430,
"storm_date": "2026-09-12",
"built_at": "2026-09-13T13:04:00.000Z",
"created_at": "2026-09-13T13:02:00.000Z",
"updated_at": "2026-09-13T13:04:00.000Z",
"url": "https://app.hailmate.ai/canvassing/lists/e5a1c7d9-aaaa-4000-8000-0000000000e7"
}
]
}Get a storm list
/storm_lists/{id}curl https://app.hailmate.ai/api/v1/storm_lists/e5a1c7d9-aaaa-4000-8000-0000000000e7 \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/storm_lists/e5a1c7d9-aaaa-4000-8000-0000000000e7', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/storm_lists/e5a1c7d9-aaaa-4000-8000-0000000000e7",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "e5a1c7d9-aaaa-4000-8000-0000000000e7",
"object": "storm_list",
"name": "Plano 1.25″+ — Sep 12",
"status": "ready",
"source": "manual",
"window_from": "2026-09-12",
"window_to": "2026-09-12",
"min_size_in": 1.25,
"area_kind": "drawn",
"territory_id": null,
"clipped_area_sq_mi": 4.2,
"property_cap": 1000,
"property_count": 412,
"skipped_duplicates": 18,
"dedupe_days": 90,
"credits_spent": 412,
"total_available": 430,
"storm_date": "2026-09-12",
"built_at": "2026-09-13T13:04:00.000Z",
"created_at": "2026-09-13T13:02:00.000Z",
"updated_at": "2026-09-13T13:04:00.000Z",
"url": "https://app.hailmate.ai/canvassing/lists/e5a1c7d9-aaaa-4000-8000-0000000000e7"
}List the properties on a storm list
/storm_lists/{id}/propertiesThe rows a mail house wants. mail_deliverable is our own judgement from the address we hold — it is NOT a CASS or NCOA result; run your own validation before you spend postage.
Path parameters
idstringuuidRequired
Query parameters
limitintegerPage size. Out-of-range values fall back to the default.
1 to 200 · Default
50cursorstringThe
next_cursorfrom the previous page, exactly as received. A cursor we did not issue is a 400.mailablestringtruedrops properties with no usable mailing address or owner name.trueresidentialstringtrue
Returns
200A page of properties. A page of StormListProperty objects in data, with has_more and next_cursor.
- 401
unauthorizedThe key is missing, wrong or revoked.
Any request can also answer 401, 402, 429 or 500 — errors.
curl "https://app.hailmate.ai/api/v1/storm_lists/e5a1c7d9-aaaa-4000-8000-0000000000e7/properties?mailable=true&limit=100" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/storm_lists/e5a1c7d9-aaaa-4000-8000-0000000000e7/properties?mailable=true&limit=100', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/storm_lists/e5a1c7d9-aaaa-4000-8000-0000000000e7/properties",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"mailable": "true", "limit": "100"},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOS0yOFQxNDowMjowMFoiLCJpIjoiZjJiMWEwYzQifQ",
"data": [
{
"id": "d4b3a2c1-bbbb-4000-8000-0000000000f8",
"object": "storm_list_property",
"storm_list_id": "e5a1c7d9-aaaa-4000-8000-0000000000e7",
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"zip": "75024",
"owner_name": "Dana Reed",
"owner_occupied": true,
"mailing_address": "1804 Cedar Ridge Dr",
"mailing_city": "Plano",
"mailing_state": "TX",
"mailing_zip": "75024",
"mail_deliverable": true,
"mail_exclude_reason": null,
"hail_size_in": 1.5,
"hail_event_date": "2026-09-12",
"nearest_report_mi": 1.8,
"nearest_report_size_in": 1.75,
"year_built": 2004,
"property_use": "Single family",
"is_residential": true,
"latitude": 33.0726,
"longitude": -96.7512,
"created_at": "2026-09-13T13:04:00.000Z"
}
]
}Endpoints
Hail
Hail and damaging-wind history at an address.
Hail and wind history at an address
/hail_historyEvery hail day and damaging-wind day at an address (or a latitude and longitude) since since — the same Evidence Grade™ ruling HailMate shows on its maps and storm reports, combining radar-estimated hail size, severe-hail probability and verified ground reports.
confidence is confirmed (a ground report backs the size), likely or radar (radar only). Default window: the last five years. United States only. Limited to 20 lookups a minute per key, inside the general limit.
Query parameters
addressstringA US street address. Or send
latitudeandlongitude.latitudenumberlongitudenumbersincestringdateYYYY-MM-DD. Defaults to five years ago.
Returns
200The history. A HailReport object.
- 400
invalid_requestSomething in the request was wrong;fieldnames what. - 429
rate_limitedOver 120 requests a minute (or 20 hail lookups a minute). WaitRetry-Afterseconds.
Any request can also answer 401, 402, 429 or 500 — errors.
curl "https://app.hailmate.ai/api/v1/hail_history?address=1804%20Cedar%20Ridge%20Dr%2C%20Plano%2C%20TX%2075024" \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/hail_history?address=1804%20Cedar%20Ridge%20Dr%2C%20Plano%2C%20TX%2075024', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/hail_history",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
params={"address": "1804 Cedar Ridge Dr, Plano, TX 75024"},
)
response.raise_for_status()
data = response.json(){
"object": "hail_report",
"address": "1804 Cedar Ridge Dr, Plano, TX 75024, USA",
"latitude": 33.0198,
"longitude": -96.6989,
"since": "2021-09-28",
"hail_day_count": 2,
"largest_hail_in": 1.5,
"last_hail_date": "2025-03-25",
"hail_events": [
{
"date": "2025-03-25",
"size_in": 1.5,
"confidence": "confirmed",
"confirmed_by_ground_report": true,
"radar_estimated_size_in": null,
"severe_hail_probability": null,
"ground_report_size_in": 1.5,
"ground_report_distance_mi": 1.4,
"ground_report_count": 11
},
{
"date": "2024-05-28",
"size_in": 1.5,
"confidence": "likely",
"confirmed_by_ground_report": false,
"radar_estimated_size_in": 1.5,
"severe_hail_probability": null,
"ground_report_size_in": 1.25,
"ground_report_distance_mi": 9,
"ground_report_count": 1
}
],
"wind_events": [
{
"date": "2025-03-04",
"max_gust_mph": 90
}
]
}Endpoints
Lookups
Pipelines, stages, teammates and the event catalogue — what a dropdown is built from.
List pipelines and their stages
/pipelinescurl https://app.hailmate.ai/api/v1/pipelines \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/pipelines', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/pipelines",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"data": [
{
"id": "1cf7d87b-7d99-4ba0-b277-0e88ac127e75",
"object": "pipeline",
"name": "Insurance",
"is_default": true,
"order": 0,
"stages": [
{
"id": "8a7b6c5d-cccc-4000-8000-000000000010",
"object": "pipeline_stage",
"pipeline_id": "1cf7d87b-7d99-4ba0-b277-0e88ac127e75",
"key": "lead",
"label": "Lead",
"order": 0,
"is_completed": false,
"is_lost": false
},
{
"id": "8a7b6c5d-cccc-4000-8000-000000000011",
"object": "pipeline_stage",
"pipeline_id": "1cf7d87b-7d99-4ba0-b277-0e88ac127e75",
"key": "claim_approved",
"label": "Claim Approved",
"order": 6,
"is_completed": false,
"is_lost": false
}
]
}
]
}List pipeline stages
/stagesEvery stage on every board. key is what you send and filter on; label is what to show a person. /pipeline_stages is the same list.
Returns
200The stages, default pipeline first, in board order. A list of PipelineStage objects in data.
- 401
unauthorizedThe key is missing, wrong or revoked.
Any request can also answer 401, 402, 429 or 500 — errors.
curl https://app.hailmate.ai/api/v1/stages \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/stages', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/stages",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"data": [
{
"id": "8a7b6c5d-cccc-4000-8000-000000000011",
"object": "pipeline_stage",
"pipeline_id": "1cf7d87b-7d99-4ba0-b277-0e88ac127e75",
"key": "claim_approved",
"label": "Claim Approved",
"order": 6,
"is_completed": false,
"is_lost": false
}
]
}List your team
/userscurl https://app.hailmate.ai/api/v1/users \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/users', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/users",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"data": [
{
"id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"object": "user",
"name": "Sam Carter",
"email": "sam@ridgelineroofing.example",
"role": "member"
}
]
}List every webhook event
/eventsReturns
200The event catalogue, with the filters each one accepts. A list of EventDefinition objects in data.
- 401
unauthorizedThe key is missing, wrong or revoked.
Any request can also answer 401, 402, 429 or 500 — errors.
curl https://app.hailmate.ai/api/v1/events \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/events', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/events",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"data": [
{
"event": "job.stage_changed",
"resource": "job",
"label": "Job Stage Changed",
"description": "A job moved to a different stage on the board.",
"filters": [
{
"key": "stage",
"label": "Stage",
"kind": "stage"
},
{
"key": "previous_stage",
"label": "Previous stage",
"kind": "stage"
},
{
"key": "job_type",
"label": "Job type",
"kind": "enum",
"values": [
"insurance",
"retail"
]
},
{
"key": "pipeline_id",
"label": "Pipeline",
"kind": "pipeline"
}
]
}
]
}Endpoints
Webhooks
Subscribe a URL to events.
curl https://app.hailmate.ai/api/v1/webhooks \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/webhooks', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/webhooks",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"data": [
{
"id": "0f8c8c1b-dddd-4000-8000-000000000022",
"object": "webhook",
"event": "job.stage_changed",
"target_url": "https://hooks.zapier.com/hooks/standard/1234567/abcdef/",
"description": "Slack: approved claims",
"filters": {
"stage": "claim_approved"
},
"created_at": "2026-09-20T18:30:00.000Z",
"disabled_at": null
}
]
}Subscribe to an event
/webhooksSend event (one event, or "*" for every event including ones added later) or events (several). The URL must be https, public, and not a private or link-local address. The response carries the signing secret — once. Several events for one URL share one secret.
filters narrows a single-event subscription, e.g. {"stage": "Claim Approved"} on job.stage_changed fires only when a job enters that stage. GET /events lists what each event accepts.
Subscribing the same event, URL and filters twice returns the existing subscription. A read-only key can subscribe.
Headers
Idempotency-KeystringAny unique string (a UUID is ideal). A retry with the same key within 24 hours replays the first answer — with
Idempotent-Replayed: true— instead of doing the work again. Reusing a key for a DIFFERENT request is a 409.Up to 255 characters
Request bodyapplication/json
eventstringOne event, or "*".
eventsarray of stringSeveral events for one URL.
target_urlstringuriRequiredtargetUrlworks too (Zapier's spelling).descriptionstringfiltersobjectOnly with a single event. See GET /events.
Returns
201Subscribed. One event returns the subscription; several return `{ "object": "list", "data": [...], "secret": "…" }`. A WebhookCreated object.
- 400
invalid_requestSomething in the request was wrong;fieldnames what.
Any request can also answer 401, 402, 429 or 500 — errors.
curl -X POST https://app.hailmate.ai/api/v1/webhooks \
-H "Authorization: Bearer $HAILMATE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"event": "job.stage_changed",
"target_url": "https://yourapp.example.com/hooks/hailmate",
"filters": {
"stage": "claim_approved"
}
}'import { randomUUID } from 'node:crypto';
const response = await fetch('https://app.hailmate.ai/api/v1/webhooks', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({
event: 'job.stage_changed',
target_url: 'https://yourapp.example.com/hooks/hailmate',
filters: {
stage: 'claim_approved',
},
}),
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import uuid
import requests
response = requests.post(
"https://app.hailmate.ai/api/v1/webhooks",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"event": "job.stage_changed",
"target_url": "https://yourapp.example.com/hooks/hailmate",
"filters": {
"stage": "claim_approved",
},
},
)
response.raise_for_status()
data = response.json(){
"id": "0f8c8c1b-dddd-4000-8000-000000000022",
"object": "webhook",
"event": "job.stage_changed",
"target_url": "https://hooks.zapier.com/hooks/standard/1234567/abcdef/",
"description": "Slack: approved claims",
"filters": {
"stage": "claim_approved"
},
"created_at": "2026-09-20T18:30:00.000Z",
"disabled_at": null,
"secret": "whsec_3a2b1c5f…"
}Get a webhook
/webhooks/{id}curl https://app.hailmate.ai/api/v1/webhooks/0f8c8c1b-dddd-4000-8000-000000000022 \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/webhooks/0f8c8c1b-dddd-4000-8000-000000000022', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/webhooks/0f8c8c1b-dddd-4000-8000-000000000022",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "0f8c8c1b-dddd-4000-8000-000000000022",
"object": "webhook",
"event": "job.stage_changed",
"target_url": "https://hooks.zapier.com/hooks/standard/1234567/abcdef/",
"description": "Slack: approved claims",
"filters": {
"stage": "claim_approved"
},
"created_at": "2026-09-20T18:30:00.000Z",
"disabled_at": null
}curl -X DELETE https://app.hailmate.ai/api/v1/webhooks/0f8c8c1b-dddd-4000-8000-000000000022 \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/webhooks/0f8c8c1b-dddd-4000-8000-000000000022', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.delete(
"https://app.hailmate.ai/api/v1/webhooks/0f8c8c1b-dddd-4000-8000-000000000022",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"object": "job",
"deleted": true
}See what an event delivers
/webhooks/samples/{event}Up to three REAL recent records from your workspace, wrapped exactly as a delivery of that event wraps them. Use it to map fields before any event has happened.
Path parameters
eventstringRequired
Returns
200Sample deliveries. A list in data.
- 404
not_foundNo such record in this workspace.
Any request can also answer 401, 402, 429 or 500 — errors.
curl https://app.hailmate.ai/api/v1/webhooks/samples/job.created \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/webhooks/samples/job.created', {
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.get(
"https://app.hailmate.ai/api/v1/webhooks/samples/job.created",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"object": "list",
"data": []
}Endpoints
OAuth
Log in with HailMate: the token endpoints for an app that connects on a person's behalf.
Get or refresh an access token
/oauth/tokenThe token endpoint of the OAuth 2.0 authorization-code flow. After the person presses Allow at https://app.hailmate.ai/oauth/authorize, your app is sent back with ?code=…&state=…; trade the code here within ten minutes. The access token lasts an hour — trade the refresh token for a new one. The refresh token does not change.
Authenticate your app with client_id and client_secret in the body or as HTTP Basic. The body may be form-encoded (the standard) or JSON. Errors use the OAuth standard's own shape: { "error": "invalid_grant", "error_description": "…" }. A code used twice disconnects the connection it made.
Request bodyapplication/x-www-form-urlencoded
grant_typestringRequiredauthorization_coderefresh_tokencodestringFor
authorization_code.redirect_uristringuriFor
authorization_code— the same address the person was sent back to.refresh_tokenstringFor
refresh_token.client_idstringRequiredclient_secretstringRequired
Returns
200A token. An OAuthToken object.
- 400
invalid_requestThe code or refresh token is not valid (invalid_grant), or the request is malformed. - 401
unauthorizedUnknown app or wrong secret (invalid_client).
Any request can also answer 401, 402, 429 or 500 — errors.
curl -X POST https://app.hailmate.ai/api/v1/oauth/token \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/oauth/token', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.post(
"https://app.hailmate.ai/api/v1/oauth/token",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){
"access_token": "hm_oat_1q2w3e4r5t6y7u8i9o0p1a2s3d4f5g6h7j8k9l0z",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "hm_ort_8d2k6m1q9x4w7c3t0r5p6n2j8h1g4f7d3s9a2l6e",
"scope": "write",
"workspace_id": "5f0c2a8e-1b3d-4c7a-9e21-7a3b8d4c6e10",
"workspace": "Ridgeline Roofing"
}Disconnect
/oauth/revokeRFC 7009. Send a refresh token to disconnect the connection — its tokens stop working and the webhooks it set up are switched off — or an access token to retire just that token. Always answers 200.
Request bodyapplication/x-www-form-urlencoded
tokenstringRequiredclient_idstringRequiredclient_secretstringRequired
Returns
200Done.
- 401
unauthorizedUnknown app or wrong secret.
Any request can also answer 401, 402, 429 or 500 — errors.
curl -X POST https://app.hailmate.ai/api/v1/oauth/revoke \
-H "Authorization: Bearer $HAILMATE_API_KEY"const response = await fetch('https://app.hailmate.ai/api/v1/oauth/revoke', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HAILMATE_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.error.code}: ${data.error.message}`);import os
import requests
response = requests.post(
"https://app.hailmate.ai/api/v1/oauth/revoke",
headers={
"Authorization": f"Bearer {os.environ['HAILMATE_API_KEY']}",
},
)
response.raise_for_status()
data = response.json(){}Objects
What the API returns
Every record carries id and object. Fields are only ever added, so ignore any you do not recognise. A webhook delivery carries the same fields at its top level.
Error
The body of every error response. Branch on error.code.
Fields
errorobjectRequirederror.codestringRequiredMachine-readable. Branch on this.
unauthorizedforbiddennot_foundinvalid_requestconflictpayload_too_largerate_limitedplan_requiredmethod_not_allowedserver_errorerror.messagestringRequiredFor a person. May be reworded.
error.fieldstringThe request field that was wrong, when there was one.
error.request_idstringerror.doc_urlstringuriRequired
{
"error": {
"code": "invalid_request",
"message": "A job needs a name.",
"field": "name",
"request_id": "req_jfvtxfboxyzopov1pi4d",
"doc_url": "https://hailmate.ai/docs/api#errors"
}
}Ping
What GET /ping returns: the workspace a key belongs to, and what the key may do.
Fields
okbooleanobjectstringalways "workspace"workspace_idstringuuidworkspacestring | nullThe workspace's name.
api_versionstringkeyobjectkey.namestringkey.accessstringreadwrite
Returned by check a key
{
"ok": true,
"object": "workspace",
"workspace_id": "5f0c2a8e-1b3d-4c7a-9e21-7a3b8d4c6e10",
"workspace": "Ridgeline Roofing",
"api_version": "v1",
"key": {
"name": "Zapier",
"access": "write"
}
}{
"id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"object": "job",
"deleted": true
}DeletedSnapshot
What a *.deleted delivery carries — the record is gone, so these are the fields that identify your copy of it.
Fields
idstringuuidobjectstringjobcontacttaskdeletedbooleanalways "true"job_numberstringnamestringaddressstringfirst_namestringlast_namestringemailstringphonestringtitlestringjob_idstringuuid
Delivered by job.deleted, contact.deleted, task.deleted
{
"id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"object": "job",
"deleted": true,
"job_number": "JOB-00142",
"name": "1804 Cedar Ridge Dr",
"address": "1804 Cedar Ridge Dr"
}PersonRef
A person on a job — the homeowner or the adjuster — in brief. Fetch the contact for everything else.
Fields
idstringuuidnamestring | nullemailstring | nullphonestring | null
{
"id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"name": "Dana Reed",
"email": "dana@example.com",
"phone": "(972) 555-0148"
}Job
The storm-restoration jobs on your pipeline boards.
Fields
idstringuuidobjectstringalways "job"job_numberstring | nullnamestring | nulljob_typestringinsuranceretailstagestring | nullThe stage key. Filter on this.
stage_labelstring | nullThe stage as your board names it.
stage_is_completedboolean | nullThe stage is a won / completed column.
stage_is_lostboolean | nullThe stage is a lost column.
stage_entered_atstring | nulldate-timepipeline_idstring | nulluuidpipeline_namestring | nulladdressstring | nullcitystring | nullstatestring | nullTwo-letter code.
postal_codestring | nullcountystring | nullhomeowner_idstring | nulluuidhomeownerPersonRef | nulladjuster_idstring | nulluuidadjusterPersonRef | nullsecondary_contact_idsarray of stringuuidsecondary_adjuster_idsarray of stringuuidassigned_to_idstring | nulluuidThe primary assignee.
assigned_to_namestring | nullassignee_idsarray of stringuuidEveryone on the job, primary included.
assignee_namesarray of stringinsurance_companystring | nullclaim_numberstring | nullpolicy_numberstring | nulldate_of_lossstring | nulldatedamage_typesarray of stringtagsarray of stringlead_sourcestring | nullprioritystring | nulllownormalhighurgentmortgage_companystring | nullfinancing_methodstring | nullcashcheckcredit_cardfinancingotherrcv_amountnumber | nullReplacement cost value on the claim.
acv_amountnumber | nullActual cash value.
deductiblenumber | nullsupplements_amountnumber | nullestimated_amountnumber | nullfinal_amountnumber | nullContract value on a retail job.
total_job_valuenumber | nullWhat the job is worth — the same figure the Money tab shows.
amount_receivednumber | nullMoney received on the job so far.
balance_duenumber | nullcontract_datestring | nulldateinstallation_datestring | nulldatearchivedbooleancompleted_atstring | nulldate-timelost_atstring | nulldate-timelost_reasonstring | nullcreated_atstringdate-timeupdated_atstringdate-timeurlstringuriOpens the job in HailMate.
portal_urlstring | nulluriThe homeowner's job page — the same link "Copy Homeowner Link" gives.
Returned by list jobs, create a job, find jobs, get a job, update a job, list a contact's jobs
Delivered by job.created, job.updated, job.stage_changed, job.assigned
{
"id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"object": "job",
"job_number": "JOB-00142",
"name": "1804 Cedar Ridge Dr",
"job_type": "insurance",
"stage": "inspection_scheduled",
"stage_label": "Inspection Scheduled",
"stage_is_completed": false,
"stage_is_lost": false,
"stage_entered_at": "2026-09-16T14:02:00.000Z",
"pipeline_id": "1cf7d87b-7d99-4ba0-b277-0e88ac127e75",
"pipeline_name": "Insurance",
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"county": "Collin",
"homeowner_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"homeowner": {
"id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"name": "Dana Reed",
"email": "dana@example.com",
"phone": "(972) 555-0148"
},
"adjuster_id": null,
"adjuster": null,
"secondary_contact_ids": [],
"secondary_adjuster_ids": [],
"assigned_to_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"assigned_to_name": "Sam Carter",
"assignee_ids": [
"5d0e1c2b-7777-4000-8000-0000000000ab"
],
"assignee_names": [
"Sam Carter"
],
"insurance_company": "Example Mutual",
"claim_number": "CLM-88213",
"policy_number": null,
"date_of_loss": "2026-09-12",
"damage_types": [
"hail"
],
"tags": [],
"lead_source": "Website",
"priority": "normal",
"mortgage_company": null,
"financing_method": null,
"rcv_amount": 24380.5,
"acv_amount": 19240,
"deductible": 2500,
"supplements_amount": null,
"estimated_amount": null,
"final_amount": null,
"total_job_value": 24380.5,
"amount_received": 0,
"balance_due": 24380.5,
"contract_date": null,
"installation_date": null,
"archived": false,
"completed_at": null,
"lost_at": null,
"lost_reason": null,
"created_at": "2026-09-16T14:02:00.000Z",
"updated_at": "2026-09-16T14:02:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa",
"portal_url": "https://app.hailmate.ai/project/0f0f0f0f-3333-4000-8000-0000000000cc"
}ContactType
What kind of contact someone is.
A string, one of:
homeownercommercialadjustercontractorsubcontractorinstall_crewinspectormortgage_companyotherContact
Homeowners, adjusters, subcontractors and everyone else in your book.
Fields
idstringuuidobjectstringalways "contact"first_namestring | nulllast_namestring | nullfull_namestring | nullemailstring | nullphonestring | nullcompanystring | nulltypestringhomeownercommercialadjustercontractorsubcontractorinstall_crewinspectormortgage_companyothertradesarray of stringFor a subcontractor — gutters, siding…
addressstring | nullcitystring | nullstatestring | nullpostal_codestring | nullnotesstring | nullclaim_numberstring | nulladjuster_typestring | nulladjuster_extensionstring | nullassigned_to_idstring | nulluuidassigned_to_namestring | nullcreated_atstringdate-timeupdated_atstringdate-timeurlstringuri
Returned by list contacts, create a contact, find contacts, get a contact, update a contact
Delivered by contact.created, contact.updated
{
"id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"object": "contact",
"first_name": "Dana",
"last_name": "Reed",
"full_name": "Dana Reed",
"email": "dana@example.com",
"phone": "(972) 555-0148",
"company": null,
"type": "homeowner",
"trades": [],
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"notes": null,
"claim_number": null,
"adjuster_type": null,
"adjuster_extension": null,
"assigned_to_id": null,
"assigned_to_name": null,
"created_at": "2026-09-16T13:40:00.000Z",
"updated_at": "2026-09-16T13:40:00.000Z",
"url": "https://app.hailmate.ai/contact/b7c2d3e4-4444-4000-8000-0000000000dd"
}Task
Tasks and appointments. An appointment is a task with an appointment_type.
Fields
idstringuuidobjectstringalways "task"titlestring | nulldescriptionstring | nulldue_datestring | nullWall-clock — the local time the crew reads. No offset.
end_datestring | nulldateThe last day of a task that blocks off several days.
duration_minutesinteger | nullprioritystring | nulllownormalhighcompletedbooleancompleted_atstring | nulldate-timeis_appointmentbooleanappointment_typestring | nullinspectionadjuster_meetingbuild_dayoutcomestring | nullcompletedno_showrescheduledcanceledcustomer_reminderstring | nullWhether HailMate reminds the homeowner the day before.
nonesmsemailbothreminder_minutesinteger | nulljob_idstring | nulluuidcontact_idstring | nulluuidassigned_to_idstring | nulluuidassigned_to_namestring | nullcreated_atstringdate-timeupdated_atstringdate-timeurlstringuri
{
"id": "d4e5f6a7-5555-4000-8000-0000000000ee",
"object": "task",
"title": "Adjuster meeting — 1804 Cedar Ridge Dr",
"description": null,
"due_date": "2026-10-02T15:00:00",
"end_date": null,
"duration_minutes": 60,
"priority": "normal",
"completed": false,
"completed_at": null,
"is_appointment": true,
"appointment_type": "adjuster_meeting",
"outcome": null,
"customer_reminder": "sms",
"reminder_minutes": 60,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"contact_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"assigned_to_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"assigned_to_name": "Sam Carter",
"created_at": "2026-09-16T14:10:00.000Z",
"updated_at": "2026-09-16T14:10:00.000Z",
"url": "https://app.hailmate.ai/task/d4e5f6a7-5555-4000-8000-0000000000ee"
}Estimate
Estimates and proposals — made from one of your templates or as your standard proposal, then finished and sent in HailMate.
Fields
idstringuuidobjectstringalways "estimate"estimate_numberstring | nulltitlestring | nulldocument_typestring | nullproposalestimatestatusstring | nulldraftsentviewedsignedexpireddeclinedsignature_statusstring | nulltotalnumber | nullThe selected package's price, else the base package. Null — never 0 — when it could not be computed.
package_totalsobject | nullEvery package's price, keyed by package.
upgrades_totalnumber | nullOptional upgrades, quoted outside the package price.
selected_tierstring | nullThe package the homeowner chose.
job_idstring | nulluuidvalid_untilstring | nulldate-timesent_atstring | nulldate-timefirst_viewed_atstring | nulldate-timelast_viewed_atstring | nulldate-timeview_countintegersigned_atstring | nulldate-timesigner_namestring | nullpdf_urlstring | nullurisigned_pdf_urlstring | nulluricreated_atstringdate-timeupdated_atstringdate-timeurlstringuriview_urlstring | nulluriThe homeowner's view of the estimate.
Returned by list a job's estimates, list estimates, create an estimate, get an estimate, update an estimate
Delivered by estimate.created, estimate.sent, estimate.viewed, estimate.signed, estimate.declined
{
"id": "a41c9e20-2222-4000-8000-0000000000e1",
"object": "estimate",
"estimate_number": "EST-0214",
"title": "Roof replacement — 1804 Cedar Ridge Dr",
"document_type": "proposal",
"status": "sent",
"signature_status": "pending",
"total": 21900,
"package_totals": {
"good": 18450,
"better": 21900,
"best": 26400
},
"upgrades_total": 1250,
"selected_tier": null,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"valid_until": "2026-10-28T00:00:00.000Z",
"sent_at": "2026-09-28T15:10:00.000Z",
"first_viewed_at": "2026-09-28T18:42:00.000Z",
"last_viewed_at": "2026-09-28T19:05:00.000Z",
"view_count": 3,
"signed_at": null,
"signer_name": null,
"pdf_url": "https://files.example-cdn.com/estimates/EST-0214.pdf",
"signed_pdf_url": null,
"created_at": "2026-09-27T21:30:00.000Z",
"updated_at": "2026-09-28T19:05:00.000Z",
"url": "https://app.hailmate.ai/estimates/a41c9e20-2222-4000-8000-0000000000e1",
"view_url": "https://app.hailmate.ai/estimate/6f1d3b52-9c7e-4a10-8f3b-2d4e5a6b7c8d"
}Invoice
Draft invoices, totalled exactly as HailMate totals them; mark them sent, or void them.
Fields
idstringuuidobjectstringalways "invoice"invoice_numberstring | nulltitlestring | nullstatusstringdraftsentviewedpartially_paidpaidoverduevoidbad_debtpurposestring | nullclaim_scopedeductiblesupplementdepreciationcontractothersubtotalnumber | nulldiscount_amountnumber | nulltax_amountnumber | nulllate_fee_amountnumber | nulltotal_amountnumberamount_paidnumberbalance_duenumbertotal_amount - amount_paid, never below zero.part_numberinteger | nullWhen billed in parts, which part this is.
part_countinteger | nullissue_datestring | nulldatedue_datestring | nulldatesent_atstring | nulldate-timefirst_viewed_atstring | nulldate-timepaid_atstring | nulldate-timejob_idstring | nulluuidcontact_idstring | nulluuidestimate_idstring | nulluuidpdf_urlstring | nulluricreated_atstringdate-timeupdated_atstringdate-timeurlstringuripayment_urlstring | nulluriThe homeowner's pay page — the same link "Copy Pay Link" gives.
{
"id": "c90d7b31-3333-4000-8000-0000000000f2",
"object": "invoice",
"invoice_number": "INV-0311",
"title": "Deductible",
"status": "sent",
"purpose": "deductible",
"subtotal": 2500,
"discount_amount": 0,
"tax_amount": 0,
"late_fee_amount": 0,
"total_amount": 2500,
"amount_paid": 0,
"balance_due": 2500,
"part_number": null,
"part_count": null,
"issue_date": "2026-09-28",
"due_date": "2026-10-12",
"sent_at": "2026-09-28T16:00:00.000Z",
"first_viewed_at": null,
"paid_at": null,
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"contact_id": "b7c2d3e4-4444-4000-8000-0000000000dd",
"estimate_id": null,
"pdf_url": "https://files.example-cdn.com/invoices/INV-0311.pdf",
"created_at": "2026-09-28T15:55:00.000Z",
"updated_at": "2026-09-28T16:00:00.000Z",
"url": "https://app.hailmate.ai/jobs/invoice/c90d7b31-3333-4000-8000-0000000000f2",
"payment_url": "https://app.hailmate.ai/invoice/0b7e2c19-5d4a-4f3e-9a1b-8c6d2e4f1a37"
}Payment
Money received against a job — carrier cheques, deductibles, card payments.
Fields
idstringuuidobjectstringalways "payment"job_idstringuuidinvoice_idstring | nulluuidamountnumberNegative for a refund.
payment_typestringacvdeductiblesupplementdepreciationretailotherpayer_typestringinsurancehomeownermortgage_companyotherpayment_methodstringcheckcashcardachfinancingotherstatusstringexpectedreceivedsent_to_mortgageendorseddepositedcheck_numberstring | nulldate_receivedstring | nulldatedate_depositedstring | nulldatenotesstring | nullis_refundbooleanrefund_of_payment_idstring | nulluuidonlinebooleanPaid through HailMate's online payment page.
created_atstringdate-timeupdated_atstringdate-timeurlstring | nulluri
Returned by list a job's payments, list payments, record a payment, get a payment
Delivered by payment.received, payment.refunded
{
"id": "9fa3e1d2-5555-4000-8000-0000000000c3",
"object": "payment",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"invoice_id": null,
"amount": 13164,
"payment_type": "acv",
"payer_type": "insurance",
"payment_method": "check",
"status": "received",
"check_number": "4471",
"date_received": "2026-09-26",
"date_deposited": null,
"notes": "First ACV check from Example Mutual",
"is_refund": false,
"refund_of_payment_id": null,
"online": false,
"created_at": "2026-09-26T20:14:00.000Z",
"updated_at": "2026-09-26T20:14:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa"
}Note
Notes on a job's timeline.
Fields
idstringuuidobjectstringalways "note"job_idstringuuidcontentstringauthor_idstring | nulluuidNull for a note written through the API.
author_namestring | nullcreated_atstringdate-timeupdated_atstringdate-timeurlstringuri
Returned by list a job's notes, list notes, add a note to a job, get a note
Delivered by note.created
{
"id": "3c8b1a07-6666-4000-8000-0000000000b4",
"object": "note",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"content": "Adjuster meeting moved to Thursday 10am. Homeowner will be home.",
"author_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"author_name": "Sam Carter",
"created_at": "2026-09-27T14:20:00.000Z",
"updated_at": "2026-09-27T14:20:00.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa"
}File
Photos and documents on a job.
Fields
idstringuuidobjectstringalways "file"job_idstring | nulluuidnamestringcategorystringphotodocumentscopeestimatecontractothermime_typestring | nullis_photobooleanis_videobooleansize_bytesinteger | nulldescriptionstring | nullThe caption.
tagsarray of stringlatitudenumber | nullWhere a photo was taken.
longitudenumber | nulltaken_atstring | nulldate-timeuploaded_by_idstring | nulluuiduploaded_by_namestring | nulldownload_urlstringuricreated_atstringdate-timeurlstring | nulluri
{
"id": "7e2d9c40-8888-4000-8000-0000000000a5",
"object": "file",
"job_id": "f2b1a0c4-1111-4000-8000-0000000000aa",
"name": "north-slope-hits.jpg",
"category": "photo",
"mime_type": "image/jpeg",
"is_photo": true,
"is_video": false,
"size_bytes": 1532410,
"description": "Hail hits on the north slope, chalked",
"tags": [
"damage",
"north slope"
],
"latitude": 33.0726,
"longitude": -96.7512,
"taken_at": "2026-09-16T15:12:08.000Z",
"uploaded_by_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"uploaded_by_name": "Sam Carter",
"download_url": "https://files.example-cdn.com/jobs/f2b1a0c4/north-slope-hits.jpg",
"created_at": "2026-09-16T15:12:40.000Z",
"url": "https://app.hailmate.ai/job/f2b1a0c4-1111-4000-8000-0000000000aa"
}KnockResult
The outcome a rep recorded at a door.
A string, one of:
interestednot_interestedcontactedno_answerdoor_hangerfollow_upappointment_scheduleddont_knockrenterno_damagecash_quotelostPin
Door knocks — pins on the canvassing map with a knock result.
Fields
idstringuuidobjectstringalways "pin"pin_typestringknockinspectionjobknock_resultstring | nullinterestednot_interestedcontactedno_answerdoor_hangerfollow_upappointment_scheduleddont_knockrenterno_damagecash_quotelostaddressstring | nullcitystring | nullstatestring | nullpostal_codestring | nulllatitudenumberlongitudenumberhomeowner_namestring | nullhomeowner_first_namestring | nullhomeowner_last_namestring | nullphonestring | nullemailstring | nullnotesstring | nulljob_idstring | nulluuidSet once the pin became a job.
created_by_idstringuuidcreated_by_namestring | nullcreated_atstringdate-timeupdated_atstringdate-timemap_urlstring | nulluriurlstringuri
Returned by list canvassing pins, log a door knock, get a pin, update a door knock
Delivered by pin.created, pin.result_changed
{
"id": "2b6f4e18-9999-4000-8000-0000000000d6",
"object": "pin",
"pin_type": "knock",
"knock_result": "interested",
"address": "1812 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"postal_code": "75024",
"latitude": 33.0731,
"longitude": -96.7519,
"homeowner_name": "Chris Lane",
"homeowner_first_name": "Chris",
"homeowner_last_name": "Lane",
"phone": "(972) 555-0193",
"email": null,
"notes": "Wants an inspection Saturday morning",
"job_id": null,
"created_by_id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"created_by_name": "Sam Carter",
"created_at": "2026-09-27T17:45:00.000Z",
"updated_at": "2026-09-27T17:45:00.000Z",
"map_url": "https://www.google.com/maps?q=33.0731,-96.7519",
"url": "https://app.hailmate.ai/canvassing"
}LineItemInput
Fields
namestringRequireddescriptionstringquantitynumberAt least 0 · Default
1unitstringsq, lf, ea…
unit_pricenumberRequired on an invoice line. Negative on an invoice is a credit.
taxablebooleanInvoices only.
Default
true
{
"name": "string",
"description": "string",
"quantity": 1,
"unit": "string",
"unit_price": 0,
"taxable": true
}EstimateTemplate
Fields
idstringuuidobjectstringalways "estimate_template"namestringtemplate_numberstring | nullcreated_atstringdate-timeupdated_atstringdate-time
Returned by list estimate templates
{
"id": "7a1c9e02-2222-4000-8000-0000000000c3",
"object": "estimate_template",
"name": "Insurance roof replacement",
"template_number": "TPL-0003",
"created_at": "2026-08-30T15:00:00.000Z",
"updated_at": "2026-09-20T18:30:00.000Z"
}EstimateTemplateList
Fields
objectstringalways "list"dataarray of EstimateTemplatehas_morebooleanalways "false"next_cursornull
{
"object": "list",
"data": [
{
"id": "7a1c9e02-2222-4000-8000-0000000000c3",
"object": "estimate_template",
"name": "Insurance roof replacement",
"template_number": "TPL-0003",
"created_at": "2026-08-30T15:00:00.000Z",
"updated_at": "2026-09-20T18:30:00.000Z"
}
],
"has_more": false,
"next_cursor": null
}OAuthTokenRequest
Fields
grant_typestringRequiredauthorization_coderefresh_tokencodestringFor
authorization_code.redirect_uristringuriFor
authorization_code— the same address the person was sent back to.refresh_tokenstringFor
refresh_token.client_idstringRequiredclient_secretstringRequired
{
"grant_type": "authorization_code",
"code": "string",
"redirect_uri": "https://app.hailmate.ai/…",
"refresh_token": "string",
"client_id": "string",
"client_secret": "string"
}OAuthRevokeRequest
Fields
tokenstringRequiredclient_idstringRequiredclient_secretstringRequired
{
"token": "string",
"client_id": "string",
"client_secret": "string"
}OAuthToken
Fields
access_tokenstringhm_oat_…— send asAuthorization: Bearer.token_typestringalways "Bearer"expires_inintegerSeconds — an hour.
refresh_tokenstringhm_ort_…— keep it secret; it does not change.scopestringreadwriteworkspace_idstringuuidworkspacestring | nullThe company that was connected.
Returned by get or refresh an access token
{
"access_token": "hm_oat_1q2w3e4r5t6y7u8i9o0p1a2s3d4f5g6h7j8k9l0z",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "hm_ort_8d2k6m1q9x4w7c3t0r5p6n2j8h1g4f7d3s9a2l6e",
"scope": "write",
"workspace_id": "5f0c2a8e-1b3d-4c7a-9e21-7a3b8d4c6e10",
"workspace": "Ridgeline Roofing"
}OAuthError
Fields
errorstringinvalid_requestinvalid_clientinvalid_grantunsupported_grant_typeserver_errorerror_descriptionstring
{
"error": "invalid_grant",
"error_description": "That code has expired. Start the connection again."
}StormList
Every property under hail of a chosen size, with the owner of record (read only).
Fields
idstringuuidobjectstringalways "storm_list"namestring | nullstatusstring | nullbuildingreadypartialfailedsourcestring | nullmanualautowindow_fromstring | nulldatewindow_tostring | nulldatemin_size_innumber | nullarea_kindstring | nullterritory_idstring | nulluuidclipped_area_sq_minumber | nullproperty_capinteger | nullproperty_countinteger | nullskipped_duplicatesinteger | nulldedupe_daysinteger | nullcredits_spentinteger | nulltotal_availableinteger | nullstorm_datestring | nulldatebuilt_atstring | nulldate-timecreated_atstringdate-timeupdated_atstringdate-timeurlstringuri
Returned by list storm lists, get a storm list
Delivered by storm_list.ready
{
"id": "e5a1c7d9-aaaa-4000-8000-0000000000e7",
"object": "storm_list",
"name": "Plano 1.25″+ — Sep 12",
"status": "ready",
"source": "manual",
"window_from": "2026-09-12",
"window_to": "2026-09-12",
"min_size_in": 1.25,
"area_kind": "drawn",
"territory_id": null,
"clipped_area_sq_mi": 4.2,
"property_cap": 1000,
"property_count": 412,
"skipped_duplicates": 18,
"dedupe_days": 90,
"credits_spent": 412,
"total_available": 430,
"storm_date": "2026-09-12",
"built_at": "2026-09-13T13:04:00.000Z",
"created_at": "2026-09-13T13:02:00.000Z",
"updated_at": "2026-09-13T13:04:00.000Z",
"url": "https://app.hailmate.ai/canvassing/lists/e5a1c7d9-aaaa-4000-8000-0000000000e7"
}StormListProperty
One property on a storm list, with the owner of record and the hail measured at that roof.
Fields
idstringuuidobjectstringalways "storm_list_property"storm_list_idstringuuidaddressstring | nullcitystring | nullstatestring | nullzipstring | nullowner_namestring | nullowner_occupiedboolean | nullNull when the county record did not say.
mailing_addressstring | nullmailing_citystring | nullmailing_statestring | nullmailing_zipstring | nullmail_deliverablebooleanOur judgement — NOT a CASS or NCOA result.
mail_exclude_reasonstring | nullhail_size_innumber | nullhail_event_datestring | nulldatenearest_report_minumber | nullnearest_report_size_innumber | nullyear_builtinteger | nullproperty_usestring | nullis_residentialboolean | nulllatitudenumber | nulllongitudenumber | nullcreated_atstringdate-time
Returned by list the properties on a storm list
{
"id": "d4b3a2c1-bbbb-4000-8000-0000000000f8",
"object": "storm_list_property",
"storm_list_id": "e5a1c7d9-aaaa-4000-8000-0000000000e7",
"address": "1804 Cedar Ridge Dr",
"city": "Plano",
"state": "TX",
"zip": "75024",
"owner_name": "Dana Reed",
"owner_occupied": true,
"mailing_address": "1804 Cedar Ridge Dr",
"mailing_city": "Plano",
"mailing_state": "TX",
"mailing_zip": "75024",
"mail_deliverable": true,
"mail_exclude_reason": null,
"hail_size_in": 1.5,
"hail_event_date": "2026-09-12",
"nearest_report_mi": 1.8,
"nearest_report_size_in": 1.75,
"year_built": 2004,
"property_use": "Single family",
"is_residential": true,
"latitude": 33.0726,
"longitude": -96.7512,
"created_at": "2026-09-13T13:04:00.000Z"
}HailEvent
One hail day at the address.
Fields
datestringdatesize_innumber | nullThe size HailMate stands behind for this day at this address.
confidencestring | nullconfirmedlikelyradarconfirmed_by_ground_reportbooleanradar_estimated_size_innumber | nullsevere_hail_probabilityinteger | nullPercent.
ground_report_size_innumber | nullground_report_distance_minumber | nullground_report_countinteger
{
"date": "2026-09-12",
"size_in": 1.5,
"confidence": "confirmed",
"confirmed_by_ground_report": true,
"radar_estimated_size_in": 1.5,
"severe_hail_probability": 80,
"ground_report_size_in": 1.75,
"ground_report_distance_mi": 1.8,
"ground_report_count": 3
}HailReport
Hail and damaging-wind history at an address.
Fields
objectstringalways "hail_report"addressstring | nullThe address as it was found.
latitudenumberlongitudenumbersincestringdatehail_day_countintegerlargest_hail_innumber | nulllast_hail_datestring | nulldatehail_eventsarray of HailEventwind_eventsarray of objectwind_events[].datestringdatewind_events[].max_gust_mphnumber | nullNull when the report was damage with no measured gust.
Returned by hail and wind history at an address
{
"object": "hail_report",
"address": "1804 Cedar Ridge Dr, Plano, TX 75024, USA",
"latitude": 33.0198,
"longitude": -96.6989,
"since": "2021-09-28",
"hail_day_count": 2,
"largest_hail_in": 1.5,
"last_hail_date": "2025-03-25",
"hail_events": [
{
"date": "2025-03-25",
"size_in": 1.5,
"confidence": "confirmed",
"confirmed_by_ground_report": true,
"radar_estimated_size_in": null,
"severe_hail_probability": null,
"ground_report_size_in": 1.5,
"ground_report_distance_mi": 1.4,
"ground_report_count": 11
},
{
"date": "2024-05-28",
"size_in": 1.5,
"confidence": "likely",
"confirmed_by_ground_report": false,
"radar_estimated_size_in": 1.5,
"severe_hail_probability": null,
"ground_report_size_in": 1.25,
"ground_report_distance_mi": 9,
"ground_report_count": 1
}
],
"wind_events": [
{
"date": "2025-03-04",
"max_gust_mph": 90
}
]
}PipelineStage
A column on one of your pipeline boards.
Fields
idstringuuidobjectstringalways "pipeline_stage"pipeline_idstringuuidkeystringWhat you send and filter on.
labelstringWhat to show a person.
orderintegeris_completedbooleanis_lostboolean
Returned by list pipeline stages
{
"id": "8a7b6c5d-cccc-4000-8000-000000000011",
"object": "pipeline_stage",
"pipeline_id": "1cf7d87b-7d99-4ba0-b277-0e88ac127e75",
"key": "claim_approved",
"label": "Claim Approved",
"order": 6,
"is_completed": false,
"is_lost": false
}Pipeline
One of your pipeline boards, with its stages in board order.
Fields
idstringuuidobjectstringalways "pipeline"namestringis_defaultbooleanorderinteger | nullstagesarray of PipelineStage
Returned by list pipelines and their stages
{
"id": "1cf7d87b-7d99-4ba0-b277-0e88ac127e75",
"object": "pipeline",
"name": "Insurance",
"is_default": true,
"order": 0,
"stages": [
{
"id": "8a7b6c5d-cccc-4000-8000-000000000010",
"object": "pipeline_stage",
"pipeline_id": "1cf7d87b-7d99-4ba0-b277-0e88ac127e75",
"key": "lead",
"label": "Lead",
"order": 0,
"is_completed": false,
"is_lost": false
},
{
"id": "8a7b6c5d-cccc-4000-8000-000000000011",
"object": "pipeline_stage",
"pipeline_id": "1cf7d87b-7d99-4ba0-b277-0e88ac127e75",
"key": "claim_approved",
"label": "Claim Approved",
"order": 6,
"is_completed": false,
"is_lost": false
}
]
}User
Someone on your team.
Fields
idstringuuidobjectstringalways "user"namestring | nullemailstring | nullrolestringowneradminmember
Returned by list your team
{
"id": "5d0e1c2b-7777-4000-8000-0000000000ab",
"object": "user",
"name": "Sam Carter",
"email": "sam@ridgelineroofing.example",
"role": "member"
}EventDefinition
A webhook event and the filters it accepts, from GET /events.
Fields
eventstringresourcestringlabelstringdescriptionstringfiltersarray of objectfilters[].keystringfilters[].labelstringfilters[].kindstringstageuserpipelineenumbooleanfilters[].valuesarray of string
Returned by list every webhook event
{
"event": "job.stage_changed",
"resource": "job",
"label": "Job Stage Changed",
"description": "A job moved to a different stage on the board.",
"filters": [
{
"key": "stage",
"label": "Stage",
"kind": "stage"
},
{
"key": "previous_stage",
"label": "Previous stage",
"kind": "stage"
},
{
"key": "job_type",
"label": "Job type",
"kind": "enum",
"values": [
"insurance",
"retail"
]
},
{
"key": "pipeline_id",
"label": "Pipeline",
"kind": "pipeline"
}
]
}Webhook
Subscribe a URL to events.
Fields
idstringuuidobjectstringalways "webhook"eventstringAn event name, or "*" for every event.
target_urlstringuridescriptionstring | nullfiltersobject | nullcreated_atstringdate-timedisabled_atstring | nulldate-time
Returned by list this key's webhooks, get a webhook
{
"id": "0f8c8c1b-dddd-4000-8000-000000000022",
"object": "webhook",
"event": "job.stage_changed",
"target_url": "https://hooks.zapier.com/hooks/standard/1234567/abcdef/",
"description": "Slack: approved claims",
"filters": {
"stage": "claim_approved"
},
"created_at": "2026-09-20T18:30:00.000Z",
"disabled_at": null
}WebhookCreated
A webhook subscription as POST /webhooks returns it — the only API response that carries its secret.
Fields
idstringuuidobjectstringalways "webhook"eventstringAn event name, or "*" for every event.
target_urlstringuridescriptionstring | nullfiltersobject | nullcreated_atstringdate-timedisabled_atstring | nulldate-timesecretstringThe signing secret. Returned ONCE.
Returned by subscribe to an event
{
"id": "0f8c8c1b-dddd-4000-8000-000000000022",
"object": "webhook",
"event": "job.stage_changed",
"target_url": "https://hooks.zapier.com/hooks/standard/1234567/abcdef/",
"description": "Slack: approved claims",
"filters": {
"stage": "claim_approved"
},
"created_at": "2026-09-20T18:30:00.000Z",
"disabled_at": null,
"secret": "whsec_3a2b1c5f…"
}EventEnvelope
Every delivery is the record's own fields at the top level plus these. Dedupe on event_id: a retry or a resend carries the same one.
Fields
event_typestringevent_idstringuuidAlso the X-HailMate-Delivery header.
event_atstringdate-timeWhen the change happened.
event_workspace_idstringuuidevent_testbooleanPresent and true on a test send from Settings.
previous_stagestring | nulljob.stage_changedonly.previous_stage_labelstring | nulljob.stage_changedonly.changed_fieldsarray of string*.updatedonly — the public field names that changed.previous_assigned_to_idstring | nulljob.assignedonly.previous_knock_resultstring | nullpin.result_changedonly.
{
"event_type": "job.stage_changed",
"event_id": "2cb27b8c-4e1f-4a3b-9d2c-6f7e8a9b0c1d",
"event_at": "2026-09-28T19:42:57.388Z",
"event_workspace_id": "5f0c2a8e-1b3d-4c7a-9e21-7a3b8d4c6e10",
"previous_stage": "inspection_complete",
"previous_stage_label": "Inspection Complete"
}