Sparky AI Reports — Integration Guide

This guide explains how to use the LiveSwitch API to run, view, and manage Sparks — Sparky's AI-generated reports — from a third-party system. It's written to be followed manually by a developer, or handed as-is to an AI coding agent to implement.

How to use this doc with an AI agent: paste this whole file into your agent's context along with your LiveSwitch API credentials (client ID, redirect URI) and say which scenario(s) from Section 4 you want implemented. Section 6 is a complete endpoint quick-reference the agent can use without re-reading the narrative sections.


1. What This Feature Does

Sparks are AI-generated reports/analyses (branded "Sparky") that LiveSwitch can run against either:

  • a Conversation — LiveSwitch's API term for what the product UI calls a Session (a single interaction: a live call, a recording request, a photo session, an upload, etc.), or
  • a Project — a container that groups multiple Conversations together (e.g. a job, a claim, an installation), useful for a rollup report across everything under it.
TermMeaning
ConversationA single session/interaction. Has a lifecycle (status) and holds recordings/photos/documents.
ProjectGroups many Conversations plus contacts/tags/attributes. Has its own status (NotStarted, InProgress, Completed).
Spark TemplateThe definition of a report type — what it analyzes, what inputs (if any) it needs. Discover these via the API; you don't create templates yourself.
Spark (run)One execution of a Spark Template against a Conversation or Project. Has a lifecycle: queuedrunningcompleted | failed | cancelled.
Task / InputA Spark Template can define one or more tasks, each with typed input fields you fill in when you start the run (e.g. free-text instructions, a required document).

2. Prerequisites & Authentication

2.1 Get API credentials

Email [email protected] with subject "Create a developer app" and include:

  • Callback/redirect URL(s)
  • App name and purpose
  • Primary developer contact

You'll receive a client_id and client_secret for OAuth2.

2.2 Base URL

https://public-api.production.liveswitch.com

All endpoints in this guide are relative to this base and prefixed /v1.

2.3 Authentication (OAuth 2.0, Bearer token)

Every request needs:

Authorization: Bearer <access_token>

Authorization Code flow (server-to-server / backend integrations — recommended for this use case):

  1. Redirect a user (once, to grant consent) to:

    GET https://id.liveswitch.com/authorize
      ?response_type=code
      &client_id={your_client_id}
      &redirect_uri={your_whitelisted_callback}
      &scope=sparks sparks.write spark-templates conversations conversations.write projects webhooks webhooks.write offline_access
      &audience=https://public-api.production.liveswitch.com/
  2. Exchange the returned code:

    POST https://id.liveswitch.com/oauth/token
    Content-Type: application/json
    
    {
      "grant_type": "authorization_code",
      "code": "...",
      "client_id": "...",
      "client_secret": "...",
      "redirect_uri": "..."
    }

    Response:

    {
      "access_token": "eyJhbGciOi...",
      "refresh_token": "v1.MjM0NTY3...",
      "expires_in": 3600,
      "token_type": "Bearer"
    }

    access_token is what you put in the Authorization: Bearer header on every API call (§2.3 above). It expires — expires_in is in seconds (e.g. 3600 = 1 hour). Save the refresh_token somewhere durable (your database/secrets store) — you'll need it in step 3, and you won't get a new one until you use it.

  3. Refresh the access token when it expires. Don't wait for a request to fail with 401 — track expires_in yourself and refresh a bit before it lapses (or refresh reactively on a 401, then retry the original request once). Call the same token endpoint, but swap the grant type and pass the refresh_token you saved in step 2 instead of a code:

    POST https://id.liveswitch.com/oauth/token
    Content-Type: application/json
    
    {
      "grant_type": "refresh_token",
      "refresh_token": "v1.MjM0NTY3...",
      "client_id": "{your_client_id}",
      "client_secret": "{your_client_secret}"
    }

    Response is the same shape as step 2 — a fresh access_token (and usually a new refresh_token; if one comes back, overwrite your saved one with it, since some auth servers rotate/invalidate the old one on use):

    {
      "access_token": "eyJhbGciOi...(new)",
      "refresh_token": "v1.NzY1NDMy...(may be a new value)",
      "expires_in": 3600,
      "token_type": "Bearer"
    }

    Use this new access_token for subsequent API calls, and repeat this step each time it expires — you never need to re-run the redirect-and-consent flow from step 1 again as long as the refresh token stays valid.

2.4 Scopes you'll need

ScopeGrants
sparksRead Spark runs (list/get)
sparks.writeCreate/delete Spark runs
spark-templatesList available report types
conversations / conversations.writeRead/create/update Conversations (sessions)
projects / projects.writeRead/create/update Projects
webhooks / webhooks.writeManage webhook subscriptions (needed for the "run when data arrives" scenario) — requires org admin rights

3. Core Concepts You Need Before Writing Code

3.1 Spark lifecycle — there is no manual "start"

When you POST a Spark, it's created in queued status immediately (202 Accepted) — even if the underlying recording/media isn't ready yet. LiveSwitch automatically promotes it to running once the media becomes available, then to completed/failed/cancelled.

queued → running → completed | failed | cancelled

This is the key mechanic for the "run a report as soon as data comes in" scenario (§4.4): you don't need to schedule anything — you request the Spark whenever you like, and the platform defers execution for you.

3.2 Conversation status — your "is there data yet?" signal

ConversationDto.status is a plain string. The value that matters for Sparks is:

ReadyForReview — at least one recording is available for this Conversation.

A Conversation moves through several other status values over its lifecycle:

StatusMeaning
NotStartedCreated, no activity yet
SentInvite/notification sent to the contact
ScheduledA Scheduled-type Conversation waiting for its startAt time
InProgressSession actively underway
ReadyForReviewAt least one recording is available — the trigger point for Sparks
ReviewedSomeone has reviewed the Conversation in the LiveSwitch app

This status is meant to be observed via webhooks, not polling — and ReadyForReview can fire more than once per Conversation if multiple recordings are added, so don't treat it as a single one-shot "done" event.

3.3 No native "report ready" event (yet)

There is no webhook event for "Spark completed" and no "Conversation ended" event either — only generic Create / Update / Delete on a fixed set of entities (Conversations, Contacts, AppInstallations, Recordings, Documents, Accounts). You must poll GET /v1/sparks/{id} (or the list endpoints) to find out when a report finishes. A dedicated "Spark completed" event is planned for a future release — this guide will be updated once it ships.


4. Scenario Walkthroughs

4.1 Discover available report types (Spark Templates)

Before running anything, find out what report types exist in your org and what inputs they need:

GET /v1/spark-templates?installed=true&limit=50
Authorization: Bearer {token}

Response (SparkTemplatesResponseDto):

{
  "data": [
    {
      "id": "template-uuid",
      "name": "Damage Assessment Summary",
      "description": "Summarizes visible damage from session photos/recordings.",
      "category": { "id": "cat-uuid", "name": "Field Service", "description": "..." },
      "requiresUserInput": true,
      "requiresPlatformLogin": false,
      "tasks": [
        {
          "id": "task-1",
          "codeName": "summary-task",
          "name": "Summary",
          "type": "user-input",
          "inputs": [
            { "key": "focusArea", "label": "Focus Area", "type": "text", "required": false, "description": "..." }
          ],
          "isRequired": true
        }
      ],
      "createdAt": "...",
      "updatedAt": "..."
    }
  ],
  "nextCursor": "opaque-cursor-string-or-null"
}

Paginate with cursor (pass back nextCursor) until it's null. Note the id (for sparkTemplateId) and each task's codeName + input keys — you'll need them in §4.2/4.3.

4.2 Run a Spark report on an existing Conversation (session)

POST /v1/conversations/{conversationId}/sparks
Authorization: Bearer {token}
Content-Type: application/json

{
  "sparkTemplateId": "template-uuid",
  "tasks": [
    { "codeName": "summary-task", "inputs": { "focusArea": "roof damage" } }
  ],
  "shareWith": ["anyone"]
}
  • All fields are optional — omit tasks entirely if the template has no user-facing inputs (requiresUserInput: false).
  • shareWith: ["anyone"] makes the finished report publicly viewable via a shareUrl (see §4.5). Omit it to keep the report private to your org.
  • Response — 202 Accepted:
    { "id": "spark-run-uuid", "status": "queued" }
  • Poll GET /v1/sparks/{id} until status is completed/failed/cancelled.

4.3 Run a Spark report on an existing Project

Identical shape, at the project level — use this for a rollup report spanning everything under a Project rather than one interaction:

POST /v1/projects/{projectId}/sparks
Authorization: Bearer {token}
Content-Type: application/json

{
  "sparkTemplateId": "template-uuid",
  "tasks": [...],
  "shareWith": ["anyone"]
}

Response is the same { "id": ..., "status": "queued" } shape. Poll the same way.

4.4 Automatically run a report as soon as session data comes in

There are two distinct situations — pick based on what triggers the report:

A. You already know the Conversation ID and just want the report the moment its recording lands.
This needs no automation at all — just call §4.2's POST immediately (e.g., right after you create the Conversation, or as soon as a user requests a report). Because Sparks start queued and auto-promote to running only once media exists, you can fire the request before the recording is even done.

B. You want to react to Conversations you don't directly control (e.g., every new session created anywhere in a project, or every time a new recording lands on an existing Conversation) — use webhooks:

  1. Subscribe:

    POST /v1/webhooks
    Authorization: Bearer {token}
    Content-Type: application/json
    
    {
      "eventType": "Update",
      "entityName": "Conversations",
      "name": "Auto-run Sparky report",
      "url": "https://your-system.example.com/webhooks/liveswitch",
      "enabled": true
    }

    (Also subscribe eventType: "Create" on Conversations if you want to catch brand-new sessions too.)

  2. Handle deliveries. Each callback includes these headers:

    HeaderMeaning
    x-liveswitch-transaction-idUnique delivery ID
    x-liveswitch-entity-idThe Conversation's ID
    x-liveswitch-webhook-idWhich subscription fired
    x-liveswitch-webhook-event-typeCREATE / UPDATE / DELETE
    x-liveswitch-webhook-entity-nameconversation

    Webhook payloads carry the entity DTO (ConversationDto for Conversation events), but not every event includes full entity detail. Treat the body as potentially incomplete: if status or other fields you need aren't present, fall back to GET /v1/conversations/{x-liveswitch-entity-id} to fetch the current state.

  3. Check for the "ready" signal, then trigger the Spark:

    def on_webhook(headers, body):
        conversation = body if "status" in body else get_conversation(headers["x-liveswitch-entity-id"])
        if conversation["status"] == "ReadyForReview":
            if not already_sparked(conversation["id"]):   # status can repeat — dedupe yourself
                create_spark_for_conversation(conversation["id"], template_id="...")

    Dedupe on your side (e.g., "have I already created a Spark for this Conversation+recording combo?") since ReadyForReview can fire again for the same Conversation when a second recording is added.

  4. Respond 2xx quickly. LiveSwitch retries a failing delivery up to 3 times on a 5xx response; a 4xx is not retried.

Webhook deliveries aren't signed today, so validate them by checking the x-liveswitch-webhook-id matches a subscription you created, and consider putting your endpoint behind a private/secret URL path.

4.5 Pull existing reports (view reports)

List all Spark runs on a Conversation:

GET /v1/conversations/{conversationId}/sparks

List all Spark runs on a Project:

GET /v1/projects/{projectId}/sparks

Both return:

{
  "data": [
    { "id": "...", "sparkTemplateId": "...", "status": "completed", "createdAt": "...", "shareUrl": "https://..." }
  ],
  "total": 3
}

Get a single Spark run (e.g., to poll status or grab its share link):

GET /v1/sparks/{id}

Returns the same fields as one data[] entry above. Neither of these list endpoints supports pagination today — not an issue at the volumes most Conversations/Projects see, but worth knowing if you're running a large number of Sparks against the same resource.

By design, SparkDto doesn't expose report content directly — only metadata (id, sparkTemplateId, status, createdAt, shareUrl). Reports can be edited afterward in the LiveSwitch app UI, so returning a content snapshot through the API risks it going stale the moment someone edits the report there. A link avoids that problem, since it always resolves to the current version. To view a finished report:

  • use the shareUrl (populated if you passed shareWith: ["anyone"] at creation — public, no login required), or
  • open the Conversation/Project in the LiveSwitch app UI (conversationUrl / projectUrl), where completed Sparks are surfaced.

If your integration needs to surface report content in your own UI, link out to shareUrl rather than trying to mirror the content — that's the only way to guarantee you're always showing the latest edited version.

4.6 "Updating" a report

By design, there's no PUT/PATCH endpoint for Spark runs. Editing a report's content is a LiveSwitch app UI action, not an API one — the only supported API mutations are Create (POST) and Delete (DELETE). If your workflow needs to change a report's inputs and regenerate it, the pattern is: DELETE the old run, then POST a new one with updated tasks[].inputs.

4.7 Delete a report

DELETE /v1/sparks/{id}

Deleting a Spark run also cancels any pending/queued schedule and cleans up the underlying analysis job — safe to call on a still-queued or running Spark to cancel it, not just on completed ones.


5. End-to-End Example: Full Automation Flow

A typical "auto-report on new field-service sessions" integration:

  1. Your system creates a Conversation via POST /v1/conversations (or a LiveSwitch user creates one in-app) — optionally linked to a Project via projectId/projectExternalId.
  2. Your webhook (subscribed per §4.4) receives an Update event once a recording is added and status becomes ReadyForReview.
  3. You call POST /v1/conversations/{id}/sparks with the appropriate sparkTemplateId, deduping so you don't double-run for the same Conversation.
  4. You poll GET /v1/sparks/{id} (e.g., every 30–60s, or on a job queue) until status is completed.
  5. You fetch the shareUrl (if sharing was enabled) and store/forward it, or direct users to the Conversation's conversationUrl in-app.
  6. If the report is no longer needed, DELETE /v1/sparks/{id}.

6. API Quick Reference

ActionMethod & PathScopeNotes
List spark templatesGET /v1/spark-templatesspark-templatesQuery: installed, limit, cursor
List sparks on a conversationGET /v1/conversations/{conversationId}/sparkssparksReturns {data[], total}
Run a spark on a conversationPOST /v1/conversations/{conversationId}/sparkssparks.writeBody: sparkTemplateId, tasks[], shareWith[]
List sparks on a projectGET /v1/projects/{projectId}/sparkssparksReturns {data[], total}
Run a spark on a projectPOST /v1/projects/{projectId}/sparkssparks.writeSame body shape as conversation
Get one spark runGET /v1/sparks/{id}sparksPoll for status
Delete a spark runDELETE /v1/sparks/{id}sparks.writeCancels queued/running runs too
Get a conversationGET /v1/conversations/{id}conversationsCheck status
List conversationsGET /v1/conversationsconversationspage, pageSize, where, order
Create a conversationPOST /v1/conversationsconversations.writetype required; see the Conversations API reference for the full request body
Update a conversationPUT /v1/conversations/{id}conversations.writeOnly contactId, notes, tags, name, projectId, assignedUserId are editable
Get a projectGET /v1/projects/{id}projects
List projectsGET /v1/projectsprojectspage, pageSize, where, order, status, externalId, contactId
Create a projectPOST /v1/projectsprojects.writename required
Update a projectPUT /v1/projects/{id}projects.write
Create webhook subscriptionPOST /v1/webhookswebhooks.writeeventType + entityName required
List webhooksGET /v1/webhookswebhooks
Get a webhookGET /v1/webhooks/{id}webhooks
Update a webhookPUT /v1/webhooks/{id}webhooks.write
Delete a webhookDELETE /v1/webhooks/{id}webhooks.write
Delete all webhooks for an appDELETE /v1/webhooks/app/{appId}webhooks.write