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.
| Term | Meaning |
|---|---|
| Conversation | A single session/interaction. Has a lifecycle (status) and holds recordings/photos/documents. |
| Project | Groups many Conversations plus contacts/tags/attributes. Has its own status (NotStarted, InProgress, Completed). |
| Spark Template | The 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: queued → running → completed | failed | cancelled. |
| Task / Input | A 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):
-
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/ -
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_tokenis what you put in theAuthorization: Bearerheader on every API call (§2.3 above). It expires —expires_inis in seconds (e.g.3600= 1 hour). Save therefresh_tokensomewhere durable (your database/secrets store) — you'll need it in step 3, and you won't get a new one until you use it. -
Refresh the access token when it expires. Don't wait for a request to fail with
401— trackexpires_inyourself and refresh a bit before it lapses (or refresh reactively on a401, then retry the original request once). Call the same token endpoint, but swap the grant type and pass therefresh_tokenyou saved in step 2 instead of acode: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 newrefresh_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_tokenfor 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
| Scope | Grants |
|---|---|
sparks | Read Spark runs (list/get) |
sparks.write | Create/delete Spark runs |
spark-templates | List available report types |
conversations / conversations.write | Read/create/update Conversations (sessions) |
projects / projects.write | Read/create/update Projects |
webhooks / webhooks.write | Manage 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:
| Status | Meaning |
|---|---|
NotStarted | Created, no activity yet |
Sent | Invite/notification sent to the contact |
Scheduled | A Scheduled-type Conversation waiting for its startAt time |
InProgress | Session actively underway |
ReadyForReview | At least one recording is available — the trigger point for Sparks |
Reviewed | Someone 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
tasksentirely if the template has no user-facing inputs (requiresUserInput: false). shareWith: ["anyone"]makes the finished report publicly viewable via ashareUrl(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}untilstatusiscompleted/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:
-
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"onConversationsif you want to catch brand-new sessions too.) -
Handle deliveries. Each callback includes these headers:
Header Meaning 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/DELETEx-liveswitch-webhook-entity-nameconversationWebhook payloads carry the entity DTO (
ConversationDtofor Conversation events), but not every event includes full entity detail. Treat the body as potentially incomplete: ifstatusor other fields you need aren't present, fall back toGET /v1/conversations/{x-liveswitch-entity-id}to fetch the current state. -
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
ReadyForReviewcan fire again for the same Conversation when a second recording is added. -
Respond
2xxquickly. LiveSwitch retries a failing delivery up to 3 times on a5xxresponse; a4xxis not retried.
Webhook deliveries aren't signed today, so validate them by checking the
x-liveswitch-webhook-idmatches 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,
SparkDtodoesn'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 passedshareWith: ["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
shareUrlrather 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:
- Your system creates a Conversation via
POST /v1/conversations(or a LiveSwitch user creates one in-app) — optionally linked to a Project viaprojectId/projectExternalId. - Your webhook (subscribed per §4.4) receives an
Updateevent once a recording is added andstatusbecomesReadyForReview. - You call
POST /v1/conversations/{id}/sparkswith the appropriatesparkTemplateId, deduping so you don't double-run for the same Conversation. - You poll
GET /v1/sparks/{id}(e.g., every 30–60s, or on a job queue) untilstatusiscompleted. - You fetch the
shareUrl(if sharing was enabled) and store/forward it, or direct users to the Conversation'sconversationUrlin-app. - If the report is no longer needed,
DELETE /v1/sparks/{id}.
6. API Quick Reference
| Action | Method & Path | Scope | Notes |
|---|---|---|---|
| List spark templates | GET /v1/spark-templates | spark-templates | Query: installed, limit, cursor |
| List sparks on a conversation | GET /v1/conversations/{conversationId}/sparks | sparks | Returns {data[], total} |
| Run a spark on a conversation | POST /v1/conversations/{conversationId}/sparks | sparks.write | Body: sparkTemplateId, tasks[], shareWith[] |
| List sparks on a project | GET /v1/projects/{projectId}/sparks | sparks | Returns {data[], total} |
| Run a spark on a project | POST /v1/projects/{projectId}/sparks | sparks.write | Same body shape as conversation |
| Get one spark run | GET /v1/sparks/{id} | sparks | Poll for status |
| Delete a spark run | DELETE /v1/sparks/{id} | sparks.write | Cancels queued/running runs too |
| Get a conversation | GET /v1/conversations/{id} | conversations | Check status |
| List conversations | GET /v1/conversations | conversations | page, pageSize, where, order |
| Create a conversation | POST /v1/conversations | conversations.write | type required; see the Conversations API reference for the full request body |
| Update a conversation | PUT /v1/conversations/{id} | conversations.write | Only contactId, notes, tags, name, projectId, assignedUserId are editable |
| Get a project | GET /v1/projects/{id} | projects | |
| List projects | GET /v1/projects | projects | page, pageSize, where, order, status, externalId, contactId |
| Create a project | POST /v1/projects | projects.write | name required |
| Update a project | PUT /v1/projects/{id} | projects.write | |
| Create webhook subscription | POST /v1/webhooks | webhooks.write | eventType + entityName required |
| List webhooks | GET /v1/webhooks | webhooks | |
| Get a webhook | GET /v1/webhooks/{id} | webhooks | |
| Update a webhook | PUT /v1/webhooks/{id} | webhooks.write | |
| Delete a webhook | DELETE /v1/webhooks/{id} | webhooks.write | |
| Delete all webhooks for an app | DELETE /v1/webhooks/app/{appId} | webhooks.write |