Public API v2.2 — build VIP into your ERP, CRM, and dashboards
Full CRUD over inspections, templates, users, and reports. Pull files and images programmatically, manage global Custom Sets, and receive signed webhooks the moment an inspection is created or completed — no more data blind spots between VIP and your systems.
Quickstart
- Open Settings → API Keys and generate a key. Choose
liveortestand the scopes the integration needs. - Copy the key immediately — it is hashed at rest and never shown again.
- Send it as a bearer token on every request.
curl https://virtualinspection.ai/api/public/v2/inspections?limit=25 \
-H "Authorization: Bearer vip_live_xxxxxxxxxxxxxxxx"Base URL: https://virtualinspection.ai/api/public/v2. All requests and responses are JSON over HTTPS. CORS is enabled, but keys must never be shipped in browser code.
Endpoint reference
Every endpoint is organization-scoped: a key only ever sees its own organization's data.
Sessions & integrations
The adapter layer CRM and contact-centre platforms use: test a key, start a live session with a one-tap SMS join link, and subscribe to completed-report events. Works with Zapier, HubSpot, Zoho, Dynamics 365, Salesforce, Zendesk, Genesys, NICE, and Procore.
- GET
/mereadTest a key — returns the organization and scopes
- POST
/sessionswriteStart a live session and text the join link (send an Idempotency-Key header to make retries safe)
- GET
/hooksreadList webhook subscriptions
- POST
/hookswriteSubscribe to an event — returns the signing secret once
- DELETE
/hooks/{id}writeUnsubscribe
id(path) — Resource ID.
- POST
/hooks/{id}/testwriteSend a signed sample event to verify your receiver
id(path) — Resource ID.
- GET
/sessions/{id}readRetrieve a session and its join link
id(path) — Resource ID.
- PATCH
/sessions/{id}writeReschedule or update a session's contact and job details
id(path) — Resource ID.
- DELETE
/sessions/{id}writeCancel a session
id(path) — Resource ID.
- GET
/hooks/{id}/deliveriesreadDelivery history for a subscription
id(path) — Resource ID.
- POST
/hooks/{id}/deliverieswriteReplay a past delivery (body: `{ delivery_id }`)
id(path) — Resource ID.
- POST
/hooks/{id}/rotate-secretwriteRotate the signing secret — the previous one stays valid for 24 hours
id(path) — Resource ID.
Inspections
Create, retrieve, and update inspections. Use this to sync jobs from an ERP or CRM and to pull results back out.
- GET
/inspectionsreadList inspections
limit(query) — Page size, 1–200. Defaults to 50.cursor(query) — Opaque cursor from the previous page.status(query) — draft, scheduled, in_progress, completed, cancelled.updated_after(query) — ISO 8601 — only records changed since.inspector_id(query) — Filter by assigned inspector.
- POST
/inspectionswriteCreate an inspection
- GET
/inspections/{id}readRetrieve an inspection
id(path) — Resource ID.
- PATCH
/inspections/{id}writeUpdate an inspection
id(path) — Resource ID.
Responses
Checklist answers for an inspection. v2.2 adds reference_id and isFlagged, plus targeting by question ID.
- GET
/inspections/{id}/responsesreadList responses (checklist items)
id(path) — Resource ID.limit(query) — Page size, 1–200. Defaults to 50.cursor(query) — Opaque cursor from the previous page.question_ids(query) — Comma-separated question IDs to retrieve.flagged(query) — true returns only flagged responses.rating(query) — Filter by rating.updated_after(query) — Only responses changed since.
Files & images
Pull captured photos, clips, and their verification metadata into your own systems.
- GET
/inspections/{id}/mediareadList inspection media with capture metadata
id(path) — Resource ID.limit(query) — Page size, 1–200. Defaults to 50.cursor(query) — Opaque cursor from the previous page.
- GET
/media/{id}/downloadreadDownload a file — 302 to a signed URL, or add ?format=json for the URL itself
id(path) — Resource ID.format(query) — Return the signed URL as JSON instead of redirecting.
Templates
List, retrieve, and clone report template structures.
- GET
/templatesreadList report templates
limit(query) — Page size, 1–200. Defaults to 50.cursor(query) — Opaque cursor from the previous page.
- GET
/templates/{id}readRetrieve a template
id(path) — Resource ID.
- POST
/templates/{id}/clonewriteClone a template
id(path) — Resource ID.
Reports
List completed reports, trigger generation, and download finished PDFs.
- GET
/reportsreadList completed reports (`limit`, `since`, `job_reference`)
- POST
/reportswriteGenerate a report PDF for an inspection
- GET
/reports/{id}readReport status
id(path) — Resource ID.
- GET
/reports/{id}/downloadreadDownload a report PDF
id(path) — Resource ID.
Users
Read organization members so your systems can map inspectors to internal identities.
- GET
/usersreadList organization members
Custom Sets
Global lists — statuses, causes, locations, part codes — referenced from fields in templates. Manage them programmatically to keep VIP consistent with your source systems.
- GET
/custom-setsreadList Custom Sets
- POST
/custom-setswriteCreate a Custom Set
- GET
/custom-sets/{id}readRetrieve a Custom Set with items
id(path) — Resource ID.
- PATCH
/custom-sets/{id}writeUpdate a Custom Set
id(path) — Resource ID.
- DELETE
/custom-sets/{id}adminDelete a Custom Set
id(path) — Resource ID.
- GET
/custom-sets/{id}/itemsreadList items
id(path) — Resource ID.
- POST
/custom-sets/{id}/itemswriteAppend items
id(path) — Resource ID.
- PUT
/custom-sets/{id}/itemswriteReplace all items
id(path) — Resource ID.
Evidence Integrity
Photo compliance, retake turnaround, and outstanding evidence requests across the workspace. Use it to feed QA dashboards or block ERP job closure while retakes are open.
- GET
/evidencereadEvidence integrity metrics
days(query) — Window size, 1-365. Defaults to 30.inspection_id(query) — Scope the metrics to a single inspection.
Smarter response filtering in v2.2
v2.2 makes retrieval far more targeted, so integrations pull only what they need:
- New fields:
reference_idandisFlaggedare returned on responses, so records line up with your own work-order numbers and exception queues. - Better targeting: fetch responses for specific
question_ids, or setflagged=trueto retrieve only flagged items. - New filters: narrow lists by
status,updated_after, andtemplate_id— ideal for incremental syncs on a schedule.
Webhooks
Subscribe endpoints in Settings → Webhooks. Choose the events you want and, optionally, exactly which fields the payload should include.
inspection.createdAn inspection was created, from the app or the API.inspection.completedAn inspection was marked complete and its results are final.report.completedA report PDF finished generating and is ready to download.media.flaggedVerification flagged a photo or clip for review.evidence.failedPhotos on a finished inspection fall short of the organization's evidence policy.evidence.retake_requestedAn inspector asked for one or more photos to be captured again.evidence.retake_fulfilledA replacement photo arrived for a retake request.
Sample payload
{
"id": "evt_01J9…",
"type": "inspection.completed",
"created_at": "2026-08-05T17:42:11.204Z",
"data": {
"inspection": {
"id": "b1f4…",
"status": "completed",
"reference_id": "WO-88213",
"address": "1420 W Grand Ave, Chicago IL",
"completed_at": "2026-08-05T17:42:09.000Z"
}
}
}Verifying the signature
Every delivery carries x-vip-signature: t=<unix>,v1=<hex>. Compute HMAC-SHA256 over `${t}.${rawBody}` with your endpoint secret and compare using a timing-safe check.
import { createHmac, timingSafeEqual } from "crypto";
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(",").map((kv) => kv.split("=", 2)),
);
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (age > 300) return false; // reject replays older than 5 minutes
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
const a = Buffer.from(parts.v1), b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}Retries: non-2xx responses and timeouts are retried with exponential backoff. Persistent failures disable the endpoint and surface a failure count in Settings, where you can re-enable it once your receiver is healthy. Deliveries can repeat, so make handlers idempotent on the event id.
Zapier & CRM integration
Connect HubSpot, Zoho, Dynamics 365, Zendesk, Genesys, Procore and hundreds of other systems without custom code. Create a key with the read and write scopes, paste it into the Zapier connection screen, and the app authenticates against GET /v2/me.
Start Inspection SessionAction —POST /v2/sessionscreates the session, mints the one-tap join link, and texts it to the customer.Report CompletedTrigger — a REST hook registered throughPOST /v2/hooks, delivering the frozenreport.completedpayload above.
Start a session from any CRM record
curl -X POST https://virtualinspection.ai/api/public/v2/sessions \
-H "Authorization: Bearer vip_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: WO-4821" \
-d '{
"customer_name": "Jane Doe",
"customer_phone": "+15551234567",
"address": "12 Elm St, Austin TX",
"job_reference": "WO-4821",
"source": "zapier",
"send_sms": true
}'Send an Idempotency-Key header so a retried automation returns the original session instead of creating a duplicate. SMS is best-effort — if it can't send, sms.sent is false with a reason and join_url is still returned so your CRM can deliver it.
Subscribe to finished reports
curl -X POST https://virtualinspection.ai/api/public/v2/hooks \
-H "Authorization: Bearer vip_live_…" \
-H "Content-Type: application/json" \
-d '{
"target_url": "https://hooks.zapier.com/hooks/catch/123/abc/",
"event_type": "report.completed"
}'
# -> { "data": { "id": "…", "secret": "whsec_…" } }The signing secret is returned once at subscribe time — store it and verify every delivery as shown above. Remove a subscription with DELETE /v2/hooks/{id}.
Rate limits & pagination
Limits are per key — 120 requests/minute by default, configurable up to 6,000 — so one integration can never starve another. Over the limit returns 429 with Retry-After.
List endpoints are cursor-paginated: pass limit (1–200) and follow next_cursor until it is absent.
Versioning
The version is pinned in the path (/v2). Additive changes — new fields, new endpoints — ship without a version bump, so parse responses tolerantly. Breaking changes get a new path and an announced deprecation window.
Errors
400 invalid_requestMalformed body or query parameter. The response names the offending field.401 unauthorizedMissing, malformed, revoked, or expired API key.403 insufficient_scopeThe key is valid but lacks the scope this endpoint requires.404 not_foundNo such resource in your organization.429 rate_limitedPer-key rate limit exceeded. Retry after the Retry-After header.500 server_errorUnexpected failure on our side. Safe to retry with backoff.
Content API for AI search engines
Our public knowledge — FAQs, industry guides, and feature documentation — is available as clean, unauthenticated JSON so assistants and crawlers can cite it accurately.
- /api/public/ai/content — catalog of every item.
/api/public/ai/content/{type}/{slug}— full text for one item (faq, industry, feature).- /llms.txt — machine-readable site map for language models.
Developer FAQ
How do I authenticate against the Public API?
Every request carries an organization API key as a bearer token: Authorization: Bearer vip_live_… Keys are created in Settings → API Keys, are scoped to read, write, or admin, and can be issued separately per integration or environment and rotated independently.
What are the API rate limits?
Each key has its own per-minute limit, 120 requests per minute by default and configurable up to 6,000. Exceeding it returns HTTP 429 with a Retry-After header. Because the limit is per key, one noisy integration cannot starve the others.
How does pagination work?
List endpoints are cursor-paginated. Pass limit (1–200, default 50) and follow the next_cursor value from each response as the cursor parameter on the following request. An absent next_cursor means you have reached the end.
How do I verify a webhook signature?
Each delivery includes x-vip-signature: t=<unix-timestamp>,v1=<hex>. Compute HMAC-SHA256 over the string `${t}.${rawBody}` using the endpoint's signing secret and compare it to v1 with a timing-safe comparison. Reject deliveries older than five minutes.
What happens when a webhook delivery fails?
Failed deliveries are retried automatically with exponential backoff across several attempts. Endpoints that keep failing are disabled and shown with a failure count in Settings → Webhooks, where you can re-enable them after fixing the receiver.
Can AI search engines read Virtual Inspection Pro content?
Yes. /api/public/ai/content returns a JSON catalog of every FAQ, industry, and feature page, and /api/public/ai/content/{type}/{slug} returns the full text of a single item. Both are unauthenticated, CORS-enabled, and listed in llms.txt.
Ready to integrate?
API access and webhooks are included with Enterprise. Generate a key in seconds, or talk to us about a guided integration with your ERP, CRM, or analytics stack.