Platform

  • Full Catalog
  • AI Capabilities
  • AI Operations Sprint
  • AI Office of the CEO
  • Solutions Map
  • ROI Calculator
  • AI Analysis

Solutions

  • Operations & Supply Chain
  • Finance & Accounting
  • Marketing & Sales
  • People & HR
  • Customer Service
  • Technology & IT
  • All departments →

Industries

  • Healthcare
  • Legal
  • Home Services & Restoration
  • Financial Services
  • Retail & E-commerce
  • Professional Services
  • All 16 industries →

Learn

  • Learning Center
  • Case Studies
  • Video Center
  • Demos
  • Research
  • Playbooks

Resources

  • Partners
  • Services
  • API Documentation
  • Integrations
  • For PuroClean Franchises
  • AI Universe

Company

  • About
  • Leadership
  • AI Charter
  • CareersHIRING
  • Blog
  • Newsroom
  • Trust Center
  • Compliance
  • DPA
  • Privacy Policy
  • Terms of Service
  • Accessibility
© 2026 Expert AI Labs. All rights reserved.
Proudly US-Based
United States
California
New York
Tennessee
Georgia

Stay Updated

Subscribe to our newsletter for the latest AI automation insights and industry trends.

← Back to interactive docsOr use Cmd/Ctrl + P → Destination: Save as PDF
EXPERT AI LABS

API Reference

Version 1.0 · Generated 2026-06-04
The orchestration layer between lead, call, job, and follow-up.

Overview

Base URL
https://www.expertailabs.ai/api/v1
Authentication
Bearer API key in the Authorization header. Keys are tenant-scoped with permissions read, write, or admin.
Format
JSON request and response bodies. UTF-8 encoded.
Rate limits
60 requests/minute default, with a burst allowance up to 120/min. Headers X-RateLimit-Remaining and Retry-After on 429 responses.
Pagination
Cursor-based on every list endpoint. Pass cursor from the previous response's next_cursor.
Errors
JSON envelope: {"error": {"code", "message", "details"}}. HTTP status follows REST conventions.
Webhooks
HMAC-SHA256 signed deliveries. Verify X-EAL-Signature against your signing secret before acting.
SDKs
Curl, Node.js (fetch), Python (requests). Official SDK packages on roadmap.

Authentication example

curl https://www.expertailabs.ai/api/v1/health \
  -H "Authorization: Bearer eal_live_a1b2c3d4..."
{
  "status": "ok",
  "version": "v1",
  "tenant_id": "8c2f0a14-0c8a-4a3a-9a4d-3e0f1d6c2a91",
  "environment": "live",
  "scopes": ["read", "write"],
  "timestamp": "2026-04-27T19:22:01.118Z"
}

Health & Identity

Status: Live

Service-level pulse and the calling identity. Use these endpoints in CI to verify connectivity and to confirm a freshly issued key is wired correctly.

GET/api/v1/healthREAD
Service pulse

Returns 200 if the v1 API surface is up. If a Bearer key is supplied, the response includes the resolved tenant_id, environment, and granted scopes, useful as a CI smoke test for newly issued keys.

Response
{
  "status": "ok",
  "version": "v1",
  "tenant_id": "8c2f0a14-0c8a-4a3a-9a4d-3e0f1d6c2a91",
  "environment": "live",
  "scopes": ["read", "write"],
  "timestamp": "2026-04-27T19:22:01.118Z"
}

Organizations

Status: Live

Tenant entities. One organization per franchise location. Organizations may have a parent_organization_id; parent-org reads require an explicit grant from each child (Q3 2026 endpoint).

GET/api/v1/organizations/meREAD
Get the calling organization

Returns the organization tied to the API key making the request.

Response
{
  "id": "8c2f0a14-0c8a-4a3a-9a4d-3e0f1d6c2a91",
  "name": "PuroClean of North Metro Atlanta",
  "industry": "restoration",
  "parent_organization_id": null,
  "settings": {
    "timezone": "America/New_York",
    "default_locale": "en-US"
  },
  "created_at": "2025-09-12T14:23:01.418Z"
}
GET/api/v1/organizationsADMIN
List child organizations (parent-org only)

Returns child organizations for parent-corp roll-up access. Requires admin-scope key on a parent organization.

Response
{
  "data": [
    {
      "id": "8c2f0a14-0c8a-4a3a-9a4d-3e0f1d6c2a91",
      "name": "PuroClean of North Metro Atlanta",
      "industry": "restoration",
      "grants": ["leads.read", "calls.read", "audit_log.read"]
    }
  ],
  "next_cursor": null
}
POST/api/v1/organizationsADMIN
Create a child organization

Create a new tenant under the calling parent organization. Returns the new organization id and an initial admin invite link.

Request body
{
  "name": "PuroClean of West Cobb",
  "industry": "restoration",
  "settings": {
    "timezone": "America/New_York"
  },
  "primary_admin_email": "owner@example.com"
}
Response
{
  "id": "5b2c7a31-4f98-4a7d-9e2b-c1f7e4a18b62",
  "name": "PuroClean of West Cobb",
  "parent_organization_id": "ab12c3d4-e5f6-7890-abcd-ef1234567890",
  "invite_url": "https://www.expertailabs.ai/invite/itok_a1f3...",
  "invite_expires_at": "2026-05-04T17:00:00.000Z",
  "created_at": "2026-04-27T17:00:12.314Z"
}
PATCH/api/v1/organizations/{id}ADMIN
Update organization settings

Update name and settings (timezone, locale, integration toggles).

Request body
{
  "settings": {
    "timezone": "America/New_York",
    "default_locale": "en-US"
  }
}
Response
{
  "id": "5b2c7a31-4f98-4a7d-9e2b-c1f7e4a18b62",
  "settings": {
    "timezone": "America/New_York",
    "default_locale": "en-US"
  },
  "updated_at": "2026-04-27T17:02:55.901Z"
}
POST/api/v1/organizations/{id}/grantsADMIN
Grant parent-corp read access

A child organization grants read-only access to its parent organization for network roll-up reporting.

Request body
{
  "parent_organization_id": "ab12c3d4-e5f6-7890-abcd-ef1234567890",
  "scopes": ["leads.read", "calls.read", "audit_log.read"],
  "expires_at": "2027-04-27T00:00:00.000Z"
}
Response
{
  "grant_id": "f4e9c2a8-7d31-4b6f-9c83-21e5d4a07b14",
  "parent_organization_id": "ab12c3d4-e5f6-7890-abcd-ef1234567890",
  "scopes": ["leads.read", "calls.read", "audit_log.read"],
  "created_at": "2026-04-27T17:09:33.001Z",
  "expires_at": "2027-04-27T00:00:00.000Z"
}

Leads

Status: Live

Inbound and outbound lead records. Each lead is bound to the calling organization (RLS enforced). The shape below matches the live Postgres schema; additional fields available on the single-record GET include AI-qualification metadata (pain_points, timeline, budget_range, ai_reasoning, qualification_completed_at).

GET/api/v1/leadsREAD
List leads

Cursor-paginated. Default limit 50, max 200. Filter by pipeline_stage, source, and created_at range (?start_at=ISO8601&end_at=ISO8601). Cursors are opaque base64url-encoded JSON.

Response
{
  "data": [
    {
      "id": "9f1c8a32-4d65-4e2b-8a91-3c0e7d4b2f18",
      "organization_id": "8c2f0a14-0c8a-4a3a-9a4d-3e0f1d6c2a91",
      "name": "Sarah Mitchell",
      "company": null,
      "email": "sarah.mitchell@example.com",
      "source": "google_ads",
      "lead_score": 87,
      "pipeline_stage": "qualified",
      "industry": null,
      "company_size": null,
      "created_at": "2026-04-27T13:22:05.114Z",
      "updated_at": "2026-04-27T13:22:09.218Z"
    }
  ],
  "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNC0yN1QxMzoyMjowNS4xMTRaIiwiaWQiOiI5ZjFjOGEzMi00ZDY1LTRlMmItOGE5MS0zYzBlN2Q0YjJmMTgifQ"
}
GET/api/v1/leads/{id}READ
Fetch a lead

Tenant-scoped: returns 404 if the lead belongs to a different organization. Returns the full record including AI-qualification metadata.

Response
{
  "id": "9f1c8a32-4d65-4e2b-8a91-3c0e7d4b2f18",
  "organization_id": "8c2f0a14-0c8a-4a3a-9a4d-3e0f1d6c2a91",
  "name": "Sarah Mitchell",
  "company": null,
  "email": "sarah.mitchell@example.com",
  "source": "google_ads",
  "lead_score": 87,
  "pipeline_stage": "qualified",
  "industry": "residential",
  "company_size": null,
  "pain_points": "Recurring basement moisture after spring rain",
  "timeline": "this_week",
  "budget_range": null,
  "ai_reasoning": "High urgency: water damage signal, recent storm event, in-radius",
  "qualification_completed_at": "2026-04-27T13:22:09.218Z",
  "created_at": "2026-04-27T13:22:05.114Z",
  "updated_at": "2026-04-27T13:22:09.218Z"
}
POST/api/v1/leadsWRITE
Create a lead

Creates a new lead. Validates email format if provided. At least one of email, or (name + company), is required. Returns 201 with the created record.

Request body
{
  "name": "Sarah Mitchell",
  "email": "sarah.mitchell@example.com",
  "source": "website",
  "industry": "residential",
  "pipeline_stage": "new"
}
Response
{
  "id": "9f1c8a32-4d65-4e2b-8a91-3c0e7d4b2f18",
  "organization_id": "8c2f0a14-0c8a-4a3a-9a4d-3e0f1d6c2a91",
  "name": "Sarah Mitchell",
  "company": null,
  "email": "sarah.mitchell@example.com",
  "source": "website",
  "lead_score": 0,
  "pipeline_stage": "new",
  "industry": "residential",
  "company_size": null,
  "created_at": "2026-04-27T13:22:05.114Z",
  "updated_at": "2026-04-27T13:22:05.114Z"
}
PATCH/api/v1/leads/{id}WRITE
Update a lead

Partial update. Whitelisted fields only: name, company, email, source, lead_score (0–100), pipeline_stage, industry, company_size, pain_points, timeline, budget_range, ai_reasoning. Every change is recorded in /audit-log as a `lead.updated` event with a per-field diff.

Request body
{
  "pipeline_stage": "won",
  "lead_score": 92
}
Response
{
  "id": "9f1c8a32-4d65-4e2b-8a91-3c0e7d4b2f18",
  "organization_id": "8c2f0a14-0c8a-4a3a-9a4d-3e0f1d6c2a91",
  "name": "Sarah Mitchell",
  "company": null,
  "email": "sarah.mitchell@example.com",
  "source": "google_ads",
  "lead_score": 92,
  "pipeline_stage": "won",
  "updated_at": "2026-04-27T18:55:42.001Z"
}
POST/api/v1/leads/{id}/scoreWRITE
Re-score a lead

Force a re-score using the latest signals. Returns the new score and explanation.

Response
{
  "id": "9f1c8a32-4d65-4e2b-8a91-3c0e7d4b2f18",
  "lead_score": 91,
  "ai_reasoning": "Recent storm event in service radius (+0.18); phone validated (+0.10); fast form completion (+0.08)",
  "scored_at": "2026-04-27T19:01:15.214Z"
}
POST/api/v1/leads/{id}/push-to-crmWRITE
Push a lead to the configured CRM

Manually trigger a CRM push if the automatic push failed or was skipped. Idempotent: re-pushing returns the existing external_id without creating a duplicate record.

Response
{
  "id": "9f1c8a32-4d65-4e2b-8a91-3c0e7d4b2f18",
  "crm_sync": {
    "vendor": "jobnimbus",
    "external_id": "jn_117422",
    "status": "ok",
    "last_synced_at": "2026-04-27T19:03:42.118Z"
  }
}

Prospects

Status: Roadmap

Outbound prospect records produced by the AI qualification pipeline (separate from inbound `leads`). Includes enrichment data, qualification verdict, and outreach state. Activates during pilot phase.

GET/api/v1/prospectsREAD
List prospects

Cursor-paginated. Filter by qualification_status, vertical, outreach_status.

Response
{
  "data": [
    {
      "id": "1c4f7d28-3a90-4e2c-9b3a-8d5f1e0c7a32",
      "company_name": "Sandy Springs Property Group",
      "contact_name": "Daniel Park",
      "contact_email": "dpark@example.com",
      "vertical": "property_management",
      "qualification_status": "qualified",
      "outreach_status": "sequence_active",
      "created_at": "2026-04-21T11:08:43.000Z"
    }
  ],
  "next_cursor": null
}
POST/api/v1/prospects/qualifyWRITE
Qualify a prospect

Triggers the AI qualification agent. Returns qualification verdict with structured rationale.

Request body
{
  "prospect_id": "1c4f7d28-3a90-4e2c-9b3a-8d5f1e0c7a32"
}
Response
{
  "prospect_id": "1c4f7d28-3a90-4e2c-9b3a-8d5f1e0c7a32",
  "qualified": true,
  "fit_score": 82,
  "rationale": [
    "Manages 28 multifamily buildings inside service radius",
    "No incumbent restoration vendor on record (last 24 months)",
    "Recent water-loss incident reported in news 2026-03"
  ],
  "qualified_at": "2026-04-27T19:08:01.901Z"
}

Content

Status: Roadmap

AI-generated blog posts, video scripts, social posts, and review responses. Includes draft, publish, and revision workflows. Activates during pilot phase.

GET/api/v1/contentREAD
List content items

Filter by type (blog | video | social | review_response), status (draft | scheduled | published), channel.

Response
{
  "data": [
    {
      "id": "ctnt_3p9w7m2q4r",
      "type": "blog",
      "title": "Water Damage Restoration in Alpharetta, What to Do in the First 24 Hours",
      "status": "published",
      "channel": "wordpress",
      "url": "https://example.com/blog/water-damage-alpharetta-first-24-hours",
      "word_count": 1842,
      "target_keyword": "water damage alpharetta",
      "published_at": "2026-04-22T15:00:00.000Z"
    }
  ],
  "next_cursor": null
}
POST/api/v1/content/blogWRITE
Generate a blog post

Generate an SEO-optimized blog draft for a target keyword and service area.

Request body
{
  "target_keyword": "mold removal roswell",
  "service_area": { "city": "Roswell", "region": "GA", "postal_code": "30075" },
  "service_type": "mold_remediation",
  "word_count_target": 2000
}
Response
{
  "id": "ctnt_7k1m4n9z",
  "type": "blog",
  "status": "draft",
  "title": "Mold Removal in Roswell, GA, What Homeowners Need to Know",
  "estimated_read_time_min": 9,
  "word_count": 2014,
  "created_at": "2026-04-27T19:14:08.118Z"
}
POST/api/v1/content/video-scriptWRITE
Generate a video script

Returns a HeyGen-ready script in scenes with B-roll suggestions, lower-third copy, and a 30s cutdown variant.

Request body
{
  "topic": "5 signs of hidden water damage in your basement",
  "format": "feature",
  "duration_seconds": 180
}
Response
{
  "id": "ctnt_v8r3p2q9",
  "type": "video",
  "status": "draft",
  "scenes": [
    { "scene": 1, "voiceover": "Hidden water damage starts where you can't see it...", "broll_suggestion": "basement wall close-up" }
  ],
  "cutdown_30s_id": "ctnt_v8r3p2q9_30s"
}
POST/api/v1/content/review-responseWRITE
Generate a customer-review response

Generates a brand-aligned response. Sentiment is auto-classified; negative reviews route to the franchise owner before any public response.

Request body
{
  "platform": "google",
  "review_id": "g_review_z2x8c4v1",
  "rating": 5,
  "review_text": "PuroClean was at our house within 45 minutes after a pipe burst...",
  "tone": "warm-professional"
}
Response
{
  "response_text": "Thank you, Sarah! We're glad we could be there fast and get the home dried out, the team appreciates the kind words.",
  "sentiment": "positive",
  "auto_publish_recommended": true
}
POST/api/v1/content/{id}/publishWRITE
Publish to configured destinations

Publish to WordPress, social, GBP, or review platform. Returns per-channel status.

Request body
{
  "channels": ["wordpress", "instagram", "facebook"],
  "scheduled_for": null
}
Response
{
  "id": "ctnt_3p9w7m2q4r",
  "results": [
    { "channel": "wordpress", "status": "published", "url": "https://example.com/blog/..." },
    { "channel": "instagram", "status": "queued", "estimated_publish_at": "2026-04-27T20:00:00.000Z" }
  ]
}

Outreach

Status: Roadmap

Email and SMS sequences, drafts, sends, replies, suppressions, and deliverability events. Activates during pilot phase.

GET/api/v1/outreach/sequencesREAD
List sequences

Email or SMS sequences with stats (sent, opened, replied, opted-out).

Response
{
  "data": [
    {
      "id": "seq_n4q8p2w7",
      "name": "Property-management cold outbound · Q2 2026",
      "channel": "email",
      "steps": 5,
      "active": true,
      "stats": { "enrolled": 184, "sent": 612, "opened": 271, "replied": 19, "opted_out": 4 }
    }
  ],
  "next_cursor": null
}
POST/api/v1/outreach/sendWRITE
Send a single ad-hoc message

One-off email or SMS. Suppression list and rate limits still apply.

Request body
{
  "channel": "sms",
  "to": "+17705550142",
  "body": "Hi Sarah, confirming our crew at 9am tomorrow. Reply HELP for help, STOP to cancel.",
  "lead_id": "9f1c8a32-4d65-4e2b-8a91-3c0e7d4b2f18"
}
Response
{
  "message_id": "msg_2k4n8q",
  "status": "queued",
  "channel": "sms",
  "queued_at": "2026-04-27T19:30:14.001Z"
}
GET/api/v1/outreach/suppressionsREAD
List suppression entries

Unsubscribes, bounces, manual additions. Each entry includes source and reason.

Response
{
  "data": [
    {
      "id": "sup_t9q4n2",
      "channel": "email",
      "address": "old.contact@example.com",
      "reason": "user_unsubscribe",
      "source": "list-unsubscribe-header",
      "added_at": "2026-04-22T08:14:11.000Z"
    }
  ],
  "next_cursor": null
}
POST/api/v1/outreach/suppressionsWRITE
Add to suppression list

Block all outbound to this address or number.

Request body
{
  "channel": "sms",
  "address": "+17705559999",
  "reason": "manual_owner_request"
}
Response
{
  "id": "sup_n2m4q8",
  "channel": "sms",
  "address": "+17705559999",
  "added_at": "2026-04-27T19:38:08.901Z"
}

Calls

Status: Roadmap

Inbound call records ingested from CallRail and other call-tracking platforms. Each record includes recording URL, transcript, AI classification, and dispatch decision. Activates during pilot phase.

GET/api/v1/callsREAD
List calls

Filter by status, classification (water | fire | mold | biohazard | sales | junk), and qualified flag.

Response
{
  "data": [
    {
      "id": "call_q4n8p2w7",
      "tracking_number": "+17705550199",
      "caller_number": "+14045550173",
      "duration_seconds": 142,
      "classification": "water",
      "qualified": true,
      "urgency": "high",
      "dispatch_decision": "auto_dispatch",
      "received_at": "2026-04-27T17:42:11.214Z"
    }
  ],
  "next_cursor": null
}
GET/api/v1/calls/{id}READ
Fetch a call

Returns the full record: signed recording URL (1-hour TTL), transcript, AI classification with confidence, dispatch decision and reason.

Response
{
  "id": "call_q4n8p2w7",
  "tracking_number": "+17705550199",
  "caller_number": "+14045550173",
  "duration_seconds": 142,
  "recording_url": "https://signed.expertailabs.ai/recordings/q4n8p2w7?exp=1745783100&sig=...",
  "transcript": "Caller: Hi, I have water coming through my basement ceiling, I think a pipe burst...",
  "classification": {
    "intent": "water",
    "confidence": 0.96,
    "urgency": "high",
    "qualified": true
  },
  "dispatch_decision": {
    "action": "auto_dispatch",
    "reason": "in_service_radius + qualified + business_hours",
    "sms_callback_sent": true,
    "job_created_in_jobnimbus": "jn_117422"
  },
  "received_at": "2026-04-27T17:42:11.214Z"
}
GET/api/v1/calls/statsREAD
Call statistics

Aggregated counts by classification and qualified flag. Default window: last 7 days. Override with ?window=24h | 7d | 30d | 90d.

Response
{
  "window": "7d",
  "total_calls": 47,
  "qualified": 18,
  "junk_filtered": 12,
  "by_intent": {
    "water": 21,
    "fire": 4,
    "mold": 6,
    "biohazard": 1,
    "sales_pitch": 8,
    "wrong_number": 4,
    "other": 3
  },
  "median_classification_seconds": 11
}

Claims (Documentation Assist)

Status: Roadmap

Insurance Documentation Assist generates structured inputs (scope items, photo summaries, room dimensions) that the operator pastes into Xactimate or the carrier portal. We do not write to Xactimate. Activates during pilot phase.

GET/api/v1/claimsREAD
List documentation jobs

Each job represents a claim documentation extraction run.

Response
{
  "data": [
    {
      "id": "clm_8w3p1m",
      "job_external_ref": "jn_117422",
      "loss_type": "water",
      "status": "ready_for_review",
      "scope_item_count": 14,
      "photo_count": 38,
      "created_at": "2026-04-27T18:09:11.000Z"
    }
  ],
  "next_cursor": null
}
POST/api/v1/claimsWRITE
Create a documentation job

Provide a CompanyCam project ID or upload photos directly via signed URLs. Extraction runs async; subscribe to claim.scope.ready webhook.

Request body
{
  "loss_type": "water",
  "source": { "vendor": "companycam", "project_id": "cc_proj_5482" },
  "job_external_ref": "jn_117422"
}
Response
{
  "id": "clm_8w3p1m",
  "status": "extracting",
  "estimated_ready_at": "2026-04-27T18:14:00.000Z",
  "created_at": "2026-04-27T18:09:11.000Z"
}
GET/api/v1/claims/{id}READ
Fetch a documentation job

Returns extracted scope items with confidence scores and photo references.

Response
{
  "id": "clm_8w3p1m",
  "loss_type": "water",
  "status": "ready_for_review",
  "rooms": [
    {
      "name": "Basement",
      "dimensions": { "length_ft": 24, "width_ft": 18, "height_ft": 8 },
      "scope_items": [
        { "code": "WTR DRY3", "description": "Tear out wet drywall, cleanup, bag for disposal", "qty": 312, "unit": "SF", "confidence": 0.94 }
      ]
    }
  ],
  "summary_pdf_url": "https://signed.expertailabs.ai/claims/clm_8w3p1m.pdf?exp=...",
  "summary_txt_url": "https://signed.expertailabs.ai/claims/clm_8w3p1m.txt?exp=..."
}
POST/api/v1/claims/{id}/reviewWRITE
Approve, edit, or reject scope items

Human-in-loop reviewer marks each line as approved, edited, or rejected. Audit-logged.

Request body
{
  "decisions": [
    { "scope_item_id": "scope_a1", "decision": "approve" },
    { "scope_item_id": "scope_a2", "decision": "edit", "qty": 410, "note": "Re-measured on site" },
    { "scope_item_id": "scope_a3", "decision": "reject", "reason": "Pre-existing damage, not from loss" }
  ]
}
Response
{
  "id": "clm_8w3p1m",
  "status": "reviewed",
  "decisions_recorded": 3,
  "reviewed_at": "2026-04-27T19:55:11.000Z"
}

Audit Log

Status: Live

Immutable record of every state-changing action in the platform. Stored in `platform_events`. Includes action, source, correlation_id, and a JSON payload describing what changed. Retained 7 years.

GET/api/v1/audit-logREAD
List audit entries

Cursor-paginated. Filter by action (e.g., lead.updated), source (api | cron | n8n | worker | system), and created_at range (?start_at=ISO8601&end_at=ISO8601). Default limit 50, max 200.

Response
{
  "data": [
    {
      "id": "a4b1c8d2-7e30-4f1a-9c83-21e5d4a07b14",
      "organization_id": "8c2f0a14-0c8a-4a3a-9a4d-3e0f1d6c2a91",
      "action": "lead.updated",
      "source": "api",
      "correlation_id": "corr_z3p7w2",
      "payload": {
        "lead_id": "9f1c8a32-4d65-4e2b-8a91-3c0e7d4b2f18",
        "diff": {
          "pipeline_stage": { "from": "qualified", "to": "won" },
          "lead_score": { "from": 87, "to": 92 }
        },
        "key_id": "k7e2c8a1-4d65-4e2b-8a91-3c0e7d4b2f18"
      },
      "created_at": "2026-04-27T18:55:42.001Z"
    }
  ],
  "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNC0yN1QxODo1NTo0Mi4wMDFaIiwiaWQiOiJhNGIxYzhkMi03ZTMwLTRmMWEtOWM4My0yMWU1ZDRhMDdiMTQifQ"
}
GET/api/v1/audit-log/{id}READ
Fetch a single entry

Tenant-scoped. Returns the full audit record including the payload diff.

Response
{
  "id": "a4b1c8d2-7e30-4f1a-9c83-21e5d4a07b14",
  "organization_id": "8c2f0a14-0c8a-4a3a-9a4d-3e0f1d6c2a91",
  "action": "lead.updated",
  "source": "api",
  "correlation_id": "corr_z3p7w2",
  "payload": {
    "lead_id": "9f1c8a32-4d65-4e2b-8a91-3c0e7d4b2f18",
    "diff": {
      "pipeline_stage": { "from": "qualified", "to": "won" },
      "lead_score": { "from": 87, "to": 92 }
    },
    "key_id": "k7e2c8a1-4d65-4e2b-8a91-3c0e7d4b2f18"
  },
  "created_at": "2026-04-27T18:55:42.001Z"
}
GET/api/v1/audit-log/export.csvREAD
Export to CSV

Stream the audit log as CSV. Same filters as the list endpoint.

Webhooks

Status: Live

Outbound event subscriptions with HMAC-SHA256 signature verification. Subscribe at registration time; the signing secret is returned ONCE, save it. Rotate at any time with a 24-hour overlap window so you can deploy the new secret on the receiver before retiring the old one.

GET/api/v1/webhooksREAD
List webhook subscriptions

Returns all webhook endpoints registered for the calling organization.

Response
{
  "data": [
    {
      "id": "f4e9c2a8-7d31-4b6f-9c83-21e5d4a07b14",
      "url": "https://crm-bridge.example.com/webhook",
      "events": ["call.qualified", "lead.created", "claim.scope.ready"],
      "description": "Production CRM bridge",
      "signing_secret_prefix": "whsec_a1b2c3",
      "enabled": true,
      "created_at": "2026-02-14T09:11:00.000Z",
      "last_delivery_at": "2026-04-27T17:42:18.001Z",
      "last_delivery_status": 200
    }
  ]
}
GET/api/v1/webhooks/{id}READ
Fetch a webhook subscription

Returns subscription metadata. The signing secret itself is never returned, only the public prefix.

Response
{
  "id": "f4e9c2a8-7d31-4b6f-9c83-21e5d4a07b14",
  "url": "https://crm-bridge.example.com/webhook",
  "events": ["call.qualified", "lead.created", "claim.scope.ready"],
  "description": "Production CRM bridge",
  "signing_secret_prefix": "whsec_a1b2c3",
  "enabled": true,
  "created_at": "2026-02-14T09:11:00.000Z",
  "last_delivery_at": "2026-04-27T17:42:18.001Z",
  "last_delivery_status": 200
}
POST/api/v1/webhooksADMIN
Register a webhook endpoint

Subscribe to one or more event types. The endpoint must be HTTPS. We return a signing secret used for HMAC verification, save it; we will not show it again. Valid event names: call.received, call.classified, call.qualified, lead.created, lead.updated, outreach.email.sent, outreach.email.replied, claim.scope.ready, review.requested, review.received, audit.log.event.

Request body
{
  "url": "https://crm-bridge.example.com/webhook",
  "events": ["call.qualified", "lead.created", "claim.scope.ready"],
  "description": "Production CRM bridge"
}
Response
{
  "id": "f4e9c2a8-7d31-4b6f-9c83-21e5d4a07b14",
  "url": "https://crm-bridge.example.com/webhook",
  "events": ["call.qualified", "lead.created", "claim.scope.ready"],
  "description": "Production CRM bridge",
  "signing_secret_prefix": "whsec_a1b2c3",
  "enabled": true,
  "created_at": "2026-02-14T09:11:00.000Z",
  "signing_secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
  "_warning": "Save this signing_secret now. It will not be shown again."
}
DELETE/api/v1/webhooks/{id}ADMIN
Delete a webhook

Stops sending events to this endpoint immediately.

Response
{
  "id": "f4e9c2a8-7d31-4b6f-9c83-21e5d4a07b14",
  "deleted": true
}
POST/api/v1/webhooks/{id}/rotate-secretADMIN
Rotate the signing secret

Returns a new secret. The previous secret remains valid for 24 hours, so you can deploy the new one on the receiver before retiring the old.

Response
{
  "id": "f4e9c2a8-7d31-4b6f-9c83-21e5d4a07b14",
  "url": "https://crm-bridge.example.com/webhook",
  "signing_secret_prefix": "whsec_z9y8x7",
  "signing_secret": "whsec_z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5k4",
  "_warning": "Save this signing_secret now. It will not be shown again. The previous secret remains valid for 24 hours."
}
GET/api/v1/webhooks/{id}/deliveriesREAD
Recent deliveries

List the last 100 delivery attempts with status, response code, latency, and retry count.

Response
{
  "data": [
    {
      "id": "whd_8w3p1m",
      "event": "call.qualified",
      "status": "delivered",
      "response_status": 200,
      "latency_ms": 142,
      "attempt": 1,
      "delivered_at": "2026-04-27T17:42:18.001Z"
    }
  ],
  "next_cursor": null
}

Webhook events

EventDescription
call.receivedNew inbound call ingested from CallRail or other tracker
call.classifiedAI classification finished; includes intent, urgency, qualified flag
call.qualifiedA call was determined to be a qualified lead
lead.createdNew lead added to the system
lead.updatedLead status, score, or fields changed
outreach.email.sentOutbound email sent
outreach.email.repliedReply received and classified
claim.scope.readyInsurance documentation extraction completed and ready for review
review.requestedCustomer was sent a review request after job completion
review.receivedNew review detected on Google or another listing surface
audit.log.eventStream of audit events; opt-in for compliance integrations
Expert AI Labs · expertailabs.ai · info@expertailabs.com
Document generated 2026-06-04. For the latest reference, visit /docs/api