Guides

Concepts, standards, and workflows for building on the SparkVox API.

API Standards

These conventions apply to all /api/v1/* endpoints unless an endpoint page notes an exception. Following them keeps integrations predictable and secure.

Base URL and versioning

  • Base URL: https://app.sparkvox.io/api/v1
  • Version is in the path (v1). Breaking changes will ship under a new version prefix.
  • Use HTTPS only. HTTP is not supported for API calls.

Requests

  • Send JSON bodies with Content-Type: application/json
  • Pass credentials only in the Authorization header: Bearer sk_sparkvox_<key>
  • Do not put API keys in query strings or request bodies
  • Use UUIDs returned by the API for project, post, and webhook IDs

Use API keys only with /api/v1/* endpoints. Authentication for details.

Responses

  • Successful responses return JSON objects (not bare arrays at the top level)
  • Errors return JSON with a single error string field (see Error handling)
  • Timestamps use ISO 8601 UTC (e.g. 2026-05-01T10:00:00Z)
  • Money fields such as processing_cost_cents are integers in cents

HTTP methods and status codes

PatternTypical status
GET resource or list200 OK
POST create (sync)201 Created
POST create (async project)202 Accepted
POST async job (e.g. generate image)202 Accepted
PATCH update200 OK
DELETE200 OK with { deleted: true }

Pagination and filtering

List endpoints use limit and offset query parameters. Defaults and maximums are documented per endpoint (projects default limit 50, max 100). Post lists support status filters.

Public endpoints

GET/api/url-metadata does not require an API key. All /api/v1/* routes require a valid key.

Outbound webhooks (your server)

  • Register HTTPS URLs only
  • Expect Content-Type: application/json and verify X-SparkVox-Signature
  • Respond with 2xx quickly; v1 does not retry failed deliveries

Not supported in v1

  • Idempotency-Key headers
  • Batch or bulk endpoints
  • GraphQL or XML payloads
  • API key scopes (one key has full v1 access for the account)

Error Handling

When a request fails, the API returns an HTTP status code and a JSON body. Integrations should branch on status first, then read the error message for logging or user-facing copy.

Error response shape

All v1 error bodies use the same structure:

json
{ "error": "Human-readable description of what went wrong." }

There is no machine-readable error code field in v1. Match on HTTP status and parse the error string if you need specific handling.

HTTP status codes

StatusWhen it happensWhat to do
400Invalid JSON, missing field, bad enum, unsupported platform, integration not connectedFix the request body or connect the provider in the app
401Missing Authorization header, malformed key, or revoked/unknown keyCheck Bearer sk_sparkvox_... and create a new key if needed
402Insufficient processing time when creating a projectSubscribe or wait for your plan to renew; listen for project.failed with insufficient_credits
403Resource exists but belongs to another userDo not retry; verify you are using the correct project/post ID
404Project, post, or webhook not foundVerify the ID; resource may have been deleted
405HTTP method not allowed on this pathSee endpoint reference for supported methods
429Read or write daily limit exceeded for this API keyBackoff until the rate window resets; see Rate Limits
500Unexpected server or pipeline failureRetry with exponential backoff; contact support if persistent

Example error responses

http
HTTP/1.1 401 Unauthorized
Content-Type: application/json

{ "error": "Invalid API key." }
http
HTTP/1.1 400 Bad Request
Content-Type: application/json

{ "error": "source_type must be 'url', 'transcript', or 'cloud'." }
http
HTTP/1.1 402 Payment Required
Content-Type: application/json

{ "error": "Insufficient credits." }

The 402 error string still says credits for API compatibility. Treat it as insufficient processing time.

Rate limit errors (429)

Each API key allows 2,000 read requests (GET) and 100 write requests (POST, PATCH, DELETE) per rolling 24-hour window. When exceeded, the response is 429 with an error message that may include the seconds until reset. Some routes also send a Retry-After header. Prefer exponential backoff rather than hammering the API.

Full rate limit policy. Rate Limits for details.

Validation and 400 errors

Common 400 messages include Invalid JSON body., field is required., Invalid status, provider not connected, and generated_content is required when scheduling. Endpoint reference pages list field-level rules; the API does not return structured field errors in v1.

Async failures (projects and images)

Project ingest and generation run asynchronously. Failures after a 202 response are surfaced via:

  • project.failed webhook with data.error (see below)
  • GET/projects/:id when status is failed and error_message is set
  • 402 on POST/projects only when billing fails before the pipeline starts

project.failed data.error values

errorMeaning
insufficient_creditsAccount did not have enough processing time to start the project
billing_errorBilling system error during charge
(string)Pipeline failure message (truncated), e.g. transcription or generation errors

For post images, poll image_url on the post: __generating__ while running, __error__ on failure, or a normal URL when ready.

Webhook delivery and signature verification. Events & signatures for details.

Retry guidance

StatusRetry?
400, 401, 403, 404, 405No - fix the client request
402No - subscribe or wait for plan renewal first
429Yes - after Retry-After or backoff
500Yes - limited retries with backoff
202 AcceptedPoll or wait for webhook - do not resubmit the same project unless intentional

Data Model

v1 exposes projects and posts as REST resources. Moments exist in the product pipeline but are not directly addressable via the API - you interact with them through posts (moment_id on each post).

Relationships

text
Account (API key owner)
  └── Project (1 ingest job)
        └── Moment (extracted insight, internal)
              └── Post (1 LinkedIn draft per moment)

Project

FieldTypeNotes
iduuidProject identifier
titlestringDisplay name you provide on create
statusenumSee project statuses below
source_typeurl | transcript | clouddevice upload (upload) not supported via API in v1
source_materialstringPodcast / Interview or Knowledge & Advisory; set on create
professional_perspectivestringPerspective slug (host, guest, full_conversation, expert, advisor, trainer)
platformsstring[]Only linkedin is used today
duration_secondsintegerBilling length; required on create
processing_cost_centsinteger | nullProcessing time charged for this project, in US cents
error_messagestring | nullSet when status is failed
created_atISO 8601Creation timestamp

Project statuses

text
pending → transcribing → (awaiting_speaker?) → extracting → generating → ready | failed

awaiting_speaker appears when diarization completes on multi-speaker recordings with Host or Guest (or Advisor) perspective. The user must select their speaker in the app before generation continues. Poll GET/projects/:id or use webhooks. Do not assume fixed timing between stages.

Post

FieldTypeNotes
iduuidPost identifier
moment_iduuidSource moment (read-only)
platformstringlinkedin in v1
generated_contentstringMain post body
first_commentstring | nullHashtags, CTA, or extra text
statusenumpending, approved, or discarded
publish_toolstring | nulllinkedin after publishing
image_urlstring | nullSee Media guide for sentinel values
created_atISO 8601Creation timestamp

Post statuses

  • pending - draft, not yet approved for publishing
  • approved - approved in SparkVox; may also be scheduled externally if provider was set on PATCH
  • discarded - rejected draft

Webhook event shape

All events include event, data, and timestamp (Unix ms). project.ready includes project_id, title, post_count, and posts_url.

HTTP status codes, error JSON shape, and retry guidance are documented in Error handling. Error handling for details.

Projects

A project represents one recording or transcript you want turned into LinkedIn post drafts. The API starts processing immediately and returns 202 - you track progress via status fields or webhooks.

Source types

source_typeRequired fieldsNotes
urlsource_url, duration_secondsPublic audio/video URL; YouTube supported
transcripttranscript, duration_secondsFull transcript text; skips transcription
cloudprovider, file_id, file_name, mime_type, duration_secondsMP3 or SRT from google-drive, dropbox, or onedrive; native Google Docs on google-drive only (application/vnd.google-apps.document, exported to text). Pro. Use source_type transcript for pasted plain text.

Device file upload (source_type: upload) is not available via the API in v1. Use url, transcript, or cloud, or create uploads in the SparkVox web app.

Perspective (source & lens)

Controls how the transcript is interpreted when writing posts. POST/projects accepts perspective slugs only. Optional source_material selects the source kind. Omitted fields use the API key owner's Settings → Project Presets defaults.

source_materialperspective slugUse when
Podcast / InterviewhostOnly the host's lines; may pause at awaiting_speaker
Podcast / InterviewguestOnly the guest's lines; may pause at awaiting_speaker
Podcast / Interviewfull_conversationWhole episode or debate-driven posts
Knowledge & AdvisoryexpertFrameworks, masterclasses, and thought leadership
Knowledge & AdvisoryadvisorStrategic advice from collaborative calls; may pause at awaiting_speaker
Knowledge & AdvisorytrainerWalkthroughs, Q&A, and demos into step-by-step modules

Slug must match source_material. Legacy pipeline ids thought_leader, expert_guest, and show_brand return 400. Transcript source_type skips transcription but does not force a perspective - use full_conversation or trainer when uploading plain transcript text.

Billing

duration_seconds is required on create and drives billing: 1 minute of processing time per minute of content, rounded up to the nearest minute. Moment extraction, post generation, and on-demand post images are included. Your balance is checked before processing starts. If a project fails after processing time was deducted, it is refunded automatically.

Processing pipeline (parity with web app)

  • Moments are scored (relevance 70+), curated to roughly eight posts per 30 minutes of content (cap 15), and may be deprioritized when you often edit or discard similar topics/styles on past projects.
  • Optional positioning claim on Brief Sparky (Settings in the app) steers moment selection and Awareness/Authority distinctiveness checks.
  • Each curated moment gets two draft candidates; the stronger hook is stored as the post you fetch via API.
  • Webhook project.ready fires when status is ready; poll GET/projects/:id for moments_found_count vs curated post count.

Tracking progress

  • Webhook (recommended): register for project.ready and project.failed
  • Polling: GET/projects/:id until status is ready or failed
  • List: GET/projects with limit and offset for dashboards

Deleting projects

DELETE/projects/:id permanently removes the project, its moments, posts, and raw audio in storage when present. This cannot be undone.

Endpoint reference for create, list, get, and delete. Create project for details.

Posts

Posts are LinkedIn drafts generated from extracted moments. v1 produces one long-form post per moment (up to roughly 15 per project, depending on content). Each post includes generated_content and an optional first_comment.

Lifecycle

  1. Posts are created with status pending when generation completes.
  2. You may edit generated_content, first_comment, and image_url via PATCH.
  3. Set status to approved to mark ready, or discarded to reject.
  4. Optionally pass provider: linkedin on PATCH to publish to LinkedIn.

Listing and filtering

GET/projects/:id/posts returns all posts for a project. Use ?status=pending|approved|discarded to filter. GET/projects/:id/posts/:postId fetches one post (useful when polling image generation).

Scheduling and publish_tool

PATCH without provider only updates SparkVox state. PATCH with provider linkedin requires status: approved in the same request body and a connected LinkedIn integration in the app. The response may include scheduled_at and job_id. publish_tool on the post reflects where it was sent.

List connected tools before scheduling. List integrations for details.

Moments

moment_id links a post to its source insight. You cannot create or delete moments via the API. If a moment fails generation, there may be no post for it.

Post formats (v1 limits)

v1 does not expose post_type or carousel_slides. All posts are single long-form LinkedIn text.

Media

Post images are optional. v1 supports two paths: SparkVox generates an AI image for you, or you upload your own file to SparkVox media storage. Source audio/video upload is a separate concern (project source_type) and is not covered here.

image_url states

ValueMeaning
nullNo image attached
__generating__AI generation in progress
__error__Generation failed
https://...Ready image (SparkVox R2 or legacy Supabase public URL)

Poll GET/projects/:id/posts/:postId after starting generation. PATCH accepts a normal https URL when setting image_url manually.

AI generation

  1. POST.../posts/:postId/generate-image (optional post_content override)
  2. Receive 202 { accepted: true }
  3. Poll GET post until image_url is a URL or __error__
  4. Approve or schedule as usual; image publishes with the post when provider is set

Generation matches in-app Generate image. Images are stored on SparkVox media (Cloudflare R2). Included with project processing time - no extra charge for on-demand post or API image generation.

Full endpoint reference. Generate image for details.

Custom upload

  1. POST.../posts/:postId/upload-url with filename (and optional mime_type, file_size_bytes)
  2. PUT file bytes to upload_url with the returned content_type
  3. PATCH post with image_url set to public_url from the upload response

Supported formats: JPG, PNG, GIF, WebP (max 50 MB); MP4 video (max 200 MB). Set mime_type to video/mp4 for video uploads. public_url is served from media.sparkvox.io.

Full endpoint reference. Image upload URL for details.

Generate vs upload

ApproachBest for
GenerateOn-brand AI visuals from post copy; no asset prep
UploadBrand photography, screenshots, or designed creatives you already have

Source content (not post images)

Projects ingest audio/video via public URL, raw transcript text, or cloud import from connected Google Drive, Dropbox, or OneDrive (source_type cloud, provider google-drive, dropbox, or onedrive). Google Drive also accepts native Google Docs (exported to text). Device file upload (source_type upload) is not available on the API in v1. Raw audio from URL or cloud projects is stored temporarily during processing and removed after transcription when applicable.