Quickstart
- An administrator creates an API key in Settings → API & Webhooks. The full key is shown once.
- Create an inspection. The link goes to the claimant by text, email or both.
- Add a webhook endpoint in the same settings page, or poll the inspection's status.
- Once an administrator releases the report, fetch it as JSON or PDF along with the photos.
curl -X POST https://inspection360.ai/api/v1/inspections \
-H "Authorization: Bearer $CIQ_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: clm-1042-initial" \
-d '{
"claimNumber": "CLM-1042",
"claimantFirstName": "Jordan",
"claimantLastName": "Lee",
"phone": "+13055550142",
"deliveryChannel": "sms"
}'
The response (HTTP 201) includes the inspection id, its
status (sent once the
message goes out), the handlingTrack and the link. Check on it any time:
curl https://inspection360.ai/api/v1/inspections/$INSPECTION_ID \
-H "Authorization: Bearer $CIQ_API_KEY"
Authentication
Send your key as Authorization: Bearer ciq_sk_live_… or in the
x-api-key header. Keys belong to one organization, are stored only as a
hash, and carry one or both scopes:
inspections:read for status, reports, photos and templates inspections:write for creating inspections
Base URL: https://inspection360.ai/api/v1
Endpoints
Generated from the OpenAPI specification. Download it for the full request and response schemas.
POST /inspections
Trigger an inspection workflow
Creates a claim + inspection request for the authenticated organization and (by default) delivers the magic link to the claimant by SMS or email. Pass an `Idempotency-Key` header to make retries safe.
Scope: inspections:write
Parameters | Name | Type | Description |
Idempotency-Key | header · string | Replays the original response if the same key is reused. |
Request body | Name | Type | Description |
claimantFirstName required | string | |
claimantLastName required | string | |
claimNumber required | string | |
lossType | "vehicle" | |
deliveryChannel | "sms" | "email" | "both" | Defaults to email if email provided, else sms. Use "both" to deliver by SMS and email together (requires both phone and email). |
phone | string | Required if deliveryChannel is sms or both. |
email | string (email) | Required if deliveryChannel is email or both. |
templateId | string (uuid) | Optional; uses the org's configured active default template for the loss type if omitted, falling back to the most recent active template. |
propertyAddress | string | The loss location for the claim. |
vehicleDescription | string | |
descriptionOfLoss | string | |
handlingTrack | "express_auto" | "express_assist" | "investigative" | "coordinated" | Express tracks (express_auto/express_assist) are gated per-organization: they require the org to have Express Claims enabled, otherwise an explicit express value is rejected with 400 express_not_enabled. When omitted, the organization's configured default is used if valid and allowed; otherwise the request defaults to investigative. |
delivery | "auto" | "return_link" | auto sends the link; return_link skips delivery and returns the magic link only. |
metadata | object | Echoed back in the response and ignored by processing. |
Responses: 200 Idempotent replay of a previous create. · 201 Inspection created. · 400 Bad request — body validation failed, no destination for the chosen channel, or an express handlingTrack was requested for an org that does not have Express Claims enabled. · 401 · 403 · 404 · 409 Conflict — a claim with the supplied claimNumber already exists, or another request with this Idempotency-Key is still processing. · 422 · 429 · 500 · 503
GET /inspections/{id}
Get inspection status and results
Scope: inspections:read
Parameters | Name | Type | Description |
id required | path · string (uuid) | |
Responses: 200 Current status and results. · 401 · 404 · 429 · 500 · 503
GET /inspections/{id}/report
Get the structured submitted report (JSON)
Returns the structured report for a submitted, administrator-released inspection: claim/claimant, visible answers, severity, photo count, sufficiency review, walkaround validation, weather, and photo-location verification. Includes links to the PDF and photo endpoints. Returns `403 report_not_released` until the report is released.
Scope: inspections:read
Parameters | Name | Type | Description |
id required | path · string (uuid) | |
Responses: 200 The structured report. · 401 · 403 · 404 · 429 · 500 · 503
GET /inspections/{id}/report.pdf
Download the report as a PDF
Streams an administrator-released PDF rendering of the report (with embedded photos) as an attachment. Returns `403 report_not_released` before release and 404 if the inspection does not exist for this organization.
Scope: inspections:read
Parameters | Name | Type | Description |
id required | path · string (uuid) | |
Responses: 200 PDF document. · 401 · 403 · 404 · 429 · 500 · 503
GET /inspections/{id}/photos
List inspection photos with signed URLs
Returns each captured photo with a short-lived signed download URL (~1 hour), step metadata, per-photo AI analysis, and capture geolocation.
Scope: inspections:read
Parameters | Name | Type | Description |
id required | path · string (uuid) | |
Responses: 200 Photo list. · 401 · 404 · 429 · 500 · 503
GET /inspections/{id}/photos.zip
Download all photos as a ZIP bundle
Streams a ZIP of all photos for the inspection (including submitted supplements) as an attachment.
Scope: inspections:read
Parameters | Name | Type | Description |
id required | path · string (uuid) | |
Responses: 200 ZIP archive. · 401 · 404 · 429 · 500 · 503
GET /templates
List the calling org's inspection templates
Lists the inspection templates belonging to the organization the API key is scoped to. Use the returned `id` values as `templateId` when triggering an inspection.
Scope: inspections:read
Parameters | Name | Type | Description |
category | query · "property" | "vehicle" | Filter to a category. Use vehicle for new work; property remains available for legacy template reads. |
active | query · boolean | Filter by active state. |
Responses: 200 List of templates for the organization. · 401 · 422 · 429 · 503
Webhooks
Add an endpoint in Settings → API & Webhooks. Each endpoint gets a signing secret, shown once.
Deliveries are POST requests with these headers:
X-CIQ-Event: the event name X-CIQ-Delivery: a unique delivery ID. Deduplicate on it. X-CIQ-Signature: t=<unix-seconds>,v1=<hmac-hex>
{
"event": "inspection.submitted",
"id": "<delivery id, equals X-CIQ-Delivery>",
"created_at": "2026-06-01T19:12:13.570Z",
"data": {
"entityType": "inspection_request",
"entityId": "<inspection request id>",
"eventType": "inspection_submitted"
}
}
Webhook events inspection.created | An inspection request was created from the dashboard or the API. |
inspection.opened | The recipient opened the inspection. |
inspection.submitted | The inspection was submitted. Includes report and photo URLs. |
inspection.analyzed | AI analysis finished. |
inspection.sufficiency_completed | The sufficiency review finished. |
inspection.supplement_created | A follow-up photo request was created. |
inspection.triage.completed | An Express triage result was produced. |
turnaround.completed | A fleet condition check was submitted, with its verdict. |
Verify every delivery
The signature is an HMAC-SHA256 of ${t}.${rawBody} using your endpoint's
secret. Verify against the raw body bytes, use a constant-time comparison, and reject timestamps older than about
five minutes.
import crypto from "node:crypto";
// Mount with a raw body parser so you verify the exact signed bytes:
// app.post("/ciq-webhook", express.raw({ type: "application/json" }), handler)
function verifyCiqWebhook(req, secret, toleranceSec = 300) {
const header = req.get("X-CIQ-Signature") || "";
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
const t = Number(parts.t);
const sig = parts.v1;
if (!t || !sig) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${req.body}`)
.digest("hex");
const a = Buffer.from(sig);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
- At-least-once delivery. Failed deliveries retry with increasing delays (1m, 5m, 30m, 2h, 6h; up to six attempts).
- Ordering isn't guaranteed. Reconcile with the status endpoint when order matters.
- Acknowledge quickly. A non-2xx response or a reply slower than 10 seconds counts as a failure.
MCP server
The Model Context Protocol server exposes the API as tools for AI agents and assistants over Streamable HTTP at
https://inspection360.ai/api/mcp. It uses the same API keys and scopes, and every tool calls the
matching REST endpoint, so validation and idempotency behave identically.
{
"mcpServers": {
"complete-iq": {
"url": "https://inspection360.ai/api/mcp",
"headers": { "Authorization": "Bearer ciq_sk_live_..." }
}
}
}
MCP tools | Tool | Scope | Endpoint |
list_templates | inspections:read | GET /templates |
create_inspection | inspections:write | POST /inspections |
get_inspection | inspections:read | GET /inspections/{id} |
get_inspection_report | inspections:read | GET /inspections/{id}/report |
list_inspection_photos | inspections:read | GET /inspections/{id}/photos |
The PDF report and photo ZIP are binary, so agents use the URLs returned by the inspection and photo tools.
Errors and rate limits
Errors use one envelope: { "error": { "code", "message" } }. Requests are
limited to 120 per minute per client IP; beyond that you'll get HTTP 429, so retry with backoff.
Error codes | HTTP | Code | Meaning |
| 400 | invalid_request | The body failed validation. The message names the field. |
| 400 | missing_destination | No phone or email for the chosen delivery channel. |
| 400 | express_not_enabled | An Express track was requested but Express isn't enabled for your organization. |
| 401 | missing_api_key | No API key was sent. |
| 401 | invalid_api_key | The key wasn't found or was revoked. |
| 403 | insufficient_scope | The key lacks the required scope. |
| 403 | report_not_released | The report exists but hasn't been released by an administrator. |
| 404 | not_found | The inspection wasn't found in your organization. |
| 404 | template_not_found | The templateId wasn't found in your organization. |
| 409 | request_in_flight | A request with the same Idempotency-Key is still processing. Retry shortly. |
| 409 | claim_number_exists | A claim with that claimNumber already exists. |
| 422 | no_template | No active template for the loss type. Pass templateId or create one. |
| 503 | auth_unavailable | Authentication is temporarily unavailable. Retry. |
Questions about the API? Email support@completeiq.com.