Developers

HomeSurvey.ai Open API

HomeSurvey.ai turns a home walk-through, filmed by the customer on a phone or by an estimator wearing AI glasses, into a reviewed moving inventory. The Open API lets a CRM or moving software start a survey, send the survey link, and receive the finished results automatically.

Version 1.0 · Updated November 6, 2026 · REST and JSON · Authenticated with an X-API-KEY header

Release note: these APIs are being released by the end of November 2026. Until then, endpoints, fields and examples on this page may change. To start building now, email rishab@homesurvey.ai with the subject "HomeSurvey.ai Open API" and we will set you up with a test account.

Email rishab@homesurvey.ai

Quick start

  1. Get a test account. Email rishab@homesurvey.ai with the subject "HomeSurvey.ai Open API". You get a sandbox URL and a test key. In production, each mover's API key is in its HomeSurvey.ai settings.
  2. Create a survey. Call Generate survey with the customer's name and your own record ID in externalReference. Store the surveyId it returns. Send the link yourself, or call Send survey link.
  3. Get the results. Register your webhook once. When the mover's estimator reviews the survey, we send the inventory to your URL.

Overview

How the Open API works: a survey is filmed with AI glasses or a phone, the HelixIQ AI survey engine builds the inventory, and the Open API sends it to your CRM or proprietary system.

Start a survey

API What it does
POST /api/external/v1/survey/generate Creates a survey and returns its link.
POST /api/external/v1/survey/{id}/send-link Sends (or re-sends) the link to the customer by email, text or both.

Get the results

API What it does
POST /api/external/v1/webhook Registers the URL where we send finished survey results.
GET /api/external/v1/webhook Shows the webhook registered for this mover.
DELETE /api/external/v1/webhook Removes it.
POST /api/external/v1/webhook/test Sends one signed sample message to your URL.
Results webhook We send the reviewed survey to your URL.
GET /api/external/v1/survey/{id} Fetches the same results on demand, for example if your endpoint was down.

Base URL: https://apiv1.go.helixiq.ai (production). A sandbox URL and test key come with your test account.

Rate limit: 60 calls a minute per API key. Above that you receive HTTP 429; wait and retry.

Authentication

Every call carries the moving company's API key in the header X-API-KEY. Each mover has its own key, found in its HomeSurvey.ai settings. Your integration uses the key of the mover you are serving, and a survey can only be reached with the key of the mover it belongs to.

All responses use one envelope:

JSON
{"status": 1, "data": { }, "message": "..."}

status 1 means success. Errors use the same envelope with status 0 and a message; for validation errors data maps each field to its problem.

HTTP code Meaning
400 Bad request.
401 Missing or wrong API key.
402 The mover has no AI survey credits left.
403 Not allowed for this key.
404 Survey not found.
429 Too many calls. Wait and retry.
500 Server error.

1. Generate survey

POST/api/external/v1/survey/generate

Creates a new survey for a customer and returns its link. No message is sent to the customer by this call; send the link yourself, or call Send survey link.

  • Credits: each new survey holds one of the mover's AI survey credits. The credit returns if the link is not used within 30 days. If the mover has no credits left, the call returns 402.
  • Safe to retry: if you send the same externalReference again within 30 days, we return the survey already created for it ("duplicate": true) instead of creating a second one.
  • Military moves: not supported. Generate survey refuses military moves (see Military moves).
  • Store surveyId (for example SV000042). It is the ID used everywhere else in the API.

Body (JSON)

Field Type Required Notes
customerName string, max 100 yes
customerEmail string (email) no Needed to send the link by email.
customerPhoneNumber string no Needed to send the link by text.
externalReference string, max 100 no Your own record ID (for example an opportunity ID). Returned in every results message.
externalSource string, max 50 no Your system's name. Returned in every results message.
moveType string no One of GENERAL, LOCAL, INTRASTATE, INTERSTATE, INTERNATIONAL, STORAGE, COMMERCIAL, AUTO, SENIOR, STUDENT, SPECIALTY. Exact match.
moveCategory string no One of RESIDENTIAL, COMMERCIAL, GENERAL.
moveDate number no Move date as a Unix timestamp in milliseconds.
originAddress string no
type string no Who the link is for: CUSTOMER (default) or SALES (your estimator films the survey; requires salesRep.email).
salesRep object no firstName, lastName, email, phoneNumber of the estimator.
surveyNote string no A note shown with the survey.

Example request

cURL
curl --location 'https://apiv1.go.helixiq.ai/api/external/v1/survey/generate' \
--header 'X-API-KEY: <api-key>' \
--header 'Content-Type: application/json' \
--data-raw '{
  "customerName": "Jane Doe",
  "customerEmail": "jane@example.com",
  "customerPhoneNumber": "+15550100199",
  "externalReference": "OPP-55821",
  "externalSource": "your-crm",
  "moveType": "LOCAL",
  "moveDate": 1796515200000,
  "originAddress": "123 Main St, San Diego, CA"
}'

Example response

JSON
{
  "status": 1,
  "data": {"id": "665f1a2b3c4d5e6f7a8b9c0d", "surveyId": "SV000042", "link": "https://homesurvey.ai/s/8f2b9c1a4e6d7f0b3c5a1e9d2f4b6a8c"},
  "message": "Survey generated successfully"
}

POST/api/external/v1/survey/{id}/send-link

Sends, or re-sends, the link of an existing survey to the customer. It does not create a new survey or a new link.

Path: id is the survey ID returned by Generate survey (for example SV000042).

Body (JSON, optional)

Field Type Notes
medium string EMAIL, SMS or BOTH. Leave it out to use the same channel as last time (email the first time). Email needs the customer's email; text messages need the customer's phone number and the mover's text messaging set up in HomeSurvey.ai. If a channel cannot be used, the call returns 400 and says why.

The response lists the channels the link actually went out on in data.sent_via.

Example request

cURL
curl --location 'https://apiv1.go.helixiq.ai/api/external/v1/survey/SV000042/send-link' \
--header 'X-API-KEY: <api-key>' \
--header 'Content-Type: application/json' \
--data '{"medium": "BOTH"}'

Example response

JSON
{
  "status": 1,
  "data": {"id": "665f1a2b3c4d5e6f7a8b9c0d", "surveyId": "SV000042", "link": "https://homesurvey.ai/s/8f2b9c1a4e6d7f0b3c5a1e9d2f4b6a8c"},
  "message": "Survey link sent successfully"
}

(data also includes "sent_via": ["EMAIL", "SMS"].)

3. Register your webhook

POST/api/external/v1/webhook

Field Type Required Notes
webhookUrl string (https) yes Where we send results.
events array no Default ["survey.reviewed"]. Add survey.completed or survey.failed if you want them.
profile string no full (default) or core. See "Take what you can use".
cURL
curl --location 'https://apiv1.go.helixiq.ai/api/external/v1/webhook' \
--header 'X-API-KEY: <api-key>' \
--header 'Content-Type: application/json' \
--data '{"webhookUrl": "https://your-endpoint.example.com/webhooks/homesurvey", "profile": "full"}'

GET /api/external/v1/webhook returns the URL, events and profile registered for this mover.

DELETE /api/external/v1/webhook removes it. POST /api/external/v1/webhook/test sends one signed sample message to your URL so you can check your endpoint.

The webhook secret used to sign messages is shown in the mover's HomeSurvey.ai settings, next to the API key.

4. Results webhook

Events

Event When it fires
survey.reviewed The mover's estimator saved the survey as reviewed. This is the inventory a mover quotes from. Sent by default.
survey.completed AI processing finished, before human review. Only if you subscribe to it.
survey.failed The survey could not be processed. Only if you subscribe to it.

If a survey is reviewed again later, we send survey.reviewed again with a higher revision. Replace the earlier data.

Take what you can use: core and full

Every results message is built so that a system which can store only part of it still gets the inventory.

  • Core: survey details, totals, and every inventory line by room (name, quantity, quantity not going, volume and weight each, category, handling). Every line stands on its own, including items packed in cartons.
  • Full: core plus cartons, crated items, questionnaire answers, voice notes, the AI move summary, item photos and a cube sheet PDF.
  • Ignore any block you do not use. Every block outside the core is optional.
  • Every message also carries notes_text: the questionnaire answers, voice notes and AI summary as one plain-text block, for systems that only have a notes field.
  • If an optional part cannot be produced, we still send the core inventory, set partial to true and list what is missing in missing.

Delivery

  • Signature: every message carries X-HomeSurvey-Signature: sha256=<hex>, an HMAC-SHA256 of the raw request body using the mover's webhook secret. Reject any message that fails the check.
  • Headers: X-HomeSurvey-Event, X-HomeSurvey-Event-Id, X-HomeSurvey-Delivery-Attempt.
  • Retries: if you do not answer with a 2xx within 10 seconds, we retry after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours.
  • Duplicates: event_id stays the same across retries. Use it to ignore repeats.
  • Files: photos, audio and the cube sheet PDF are links that expire after 7 days. Download what you want to keep.
  • Units: units is imperial (cu ft, lbs) or metric (CBM, kg). Every volume and weight in a message uses the same units.

Checking the signature (Python)

Python
import hmac, hashlib

def is_valid(raw_body: bytes, header: str, secret: str) -> bool:
    if not header.startswith("sha256="):
        return False
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header[7:])

Message contents

Block What it holds
schema_version, event, event_id, sent_at, revision, review_status Message details. review_status is reviewed or ai_only.
survey Survey ID, your external_reference and external_source, customer name, email and phone, move type and category, move date, origin address, how the survey was filmed (customer_phone, estimator_phone, ai_glasses), units, when it was submitted and reviewed, who reviewed it, the reviewer's notes.
totals Total volume and weight, item volume and weight, packed volume and weight, line count, piece count, carton count, crate count, lines not going.
rooms[].items[] Item ID, name, quantity, quantity not going, size, volume and weight each, category, handling (two_man, heavy, fragile, needs_crate, disassemble, antique, high_value, third_party_service), high value and declared value, whether it is packed in a carton, whether the estimator added it, whether it came from the customer's voice note, the reviewer's note, photo link.
cartons[] Carton type, capacity, count, packed by the mover (CP) or by the owner (PBO), the item IDs inside.
crated_items[] Item, room, quantity, crate dimensions and volume, weight, handling, declared value, note.
questionnaire[] Question, room (if any), answer.
voice_notes[] Room, time in the video, the note in words, the item IDs it refers to, audio link.
ai_summary Scope, customer summary, customer priorities, estimator tips, risk flags, crew recommendation, room summaries.
documents Cube sheet PDF link.
notes_text Questionnaire, voice notes and AI summary as plain text.
partial, missing Set when an optional part could not be produced.

Example

JSON
{
  "schema_version": "1.0",
  "event": "survey.reviewed",
  "event_id": "evt_01JC8Z6Q2K4M",
  "sent_at": "2026-11-20T18:04:11Z",
  "revision": 1,
  "review_status": "reviewed",
  "survey": {
    "survey_id": "SV000042",
    "external_reference": "OPP-55821",
    "external_source": "your-crm",
    "customer": {"name": "Jane Doe", "email": "jane@example.com", "phone": "+1 555 010 0199"},
    "move_type": "LOCAL", "move_category": "RESIDENTIAL",
    "move_date": "2026-12-05", "origin_address": "123 Main St, San Diego, CA",
    "survey_method": "ai_glasses", "units": "imperial",
    "submitted_at": "2026-11-20T16:40:02Z", "reviewed_at": "2026-11-20T18:03:55Z",
    "reviewed_by": {"name": "Sam Estimator", "email": "sam@mover.example"},
    "reviewer_notes": "Piano goes with a third-party mover."
  },
  "totals": {
    "total_volume": 412.5, "total_weight": 2888, "item_volume": 356.0, "item_weight": 2492,
    "packed_volume": 56.5, "packed_weight": 396, "items_count": 48, "pieces_count": 61,
    "carton_count": 22, "crate_count": 1, "excluded_count": 3
  },
  "rooms": [
    {"room_id": "living", "room_name": "Living Room", "items": [
      {"item_id": "i-14", "name": "Sofa 3-Seater", "quantity": 1, "quantity_excluded": 0, "size": "large",
       "volume_each": 60, "weight_each": 420, "category": "Sofa",
       "handling": ["two_man", "heavy"], "high_value": false, "declared_value": null,
       "in_carton": false, "added_by_reviewer": false, "from_voice_note": false,
       "note": null, "photo_url": "https://files.homesurvey.ai/s/..."}
    ]}
  ],
  "cartons": [
    {"carton_id": "c-3", "room_id": "living", "carton_type": "3.0 cu ft box",
     "capacity": 3.0, "count": 4, "packing": "CP", "contents": ["i-21", "i-22"]}
  ],
  "questionnaire": [{"question": "Do you have stairs at origin?", "room_name": null, "answer": "Yes, one flight"}],
  "voice_notes": [{"room_name": "Garage", "at_seconds": 42.5, "text": "The workbench stays.",
                   "item_ids": ["i-77"], "audio_url": "https://files.homesurvey.ai/s/..."}],
  "ai_summary": {"scope": "...", "customer_summary": "...", "customer_priorities": ["..."],
                 "estimator_tips": ["..."], "risk_flags": ["..."], "crew_recommendation": "...",
                 "room_summaries": {"Living Room": "..."}},
  "documents": {"cube_sheet_pdf_url": "https://files.homesurvey.ai/s/..."},
  "notes_text": "QUESTIONNAIRE\nStairs at origin: Yes, one flight\n\nVOICE NOTES\nGarage: The workbench stays.\n\nAI SUMMARY\n...",
  "partial": false,
  "missing": []
}

5. Fetch results on demand

GET/api/external/v1/survey/{id}?profile=full

Returns the same body as the results message, for a survey that belongs to this mover's key. review_status tells you whether it has been reviewed yet. Use it if your endpoint missed a message, or to check a survey's state.

Military moves

Military moves are not in scope for the Open API. Generate survey refuses military moves, and results for military moves are not sent to outside systems.