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
| Pattern | Typical status |
|---|---|
| GET resource or list | 200 OK |
| POST create (sync) | 201 Created |
| POST create (async project) | 202 Accepted |
| POST async job (e.g. generate image) | 202 Accepted |
| PATCH update | 200 OK |
| DELETE | 200 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:
{ "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
| Status | When it happens | What to do |
|---|---|---|
| 400 | Invalid JSON, missing field, bad enum, unsupported platform, integration not connected | Fix the request body or connect the provider in the app |
| 401 | Missing Authorization header, malformed key, or revoked/unknown key | Check Bearer sk_sparkvox_... and create a new key if needed |
| 402 | Insufficient processing time when creating a project | Subscribe or wait for your plan to renew; listen for project.failed with insufficient_credits |
| 403 | Resource exists but belongs to another user | Do not retry; verify you are using the correct project/post ID |
| 404 | Project, post, or webhook not found | Verify the ID; resource may have been deleted |
| 405 | HTTP method not allowed on this path | See endpoint reference for supported methods |
| 429 | Read or write daily limit exceeded for this API key | Backoff until the rate window resets; see Rate Limits |
| 500 | Unexpected server or pipeline failure | Retry with exponential backoff; contact support if persistent |
Example error responses
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{ "error": "Invalid API key." }HTTP/1.1 400 Bad Request
Content-Type: application/json
{ "error": "source_type must be 'url', 'transcript', or 'cloud'." }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/:idwhen status is failed and error_message is set - 402 on POST
/projectsonly when billing fails before the pipeline starts
project.failed data.error values
| error | Meaning |
|---|---|
| insufficient_credits | Account did not have enough processing time to start the project |
| billing_error | Billing 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
| Status | Retry? |
|---|---|
| 400, 401, 403, 404, 405 | No - fix the client request |
| 402 | No - subscribe or wait for plan renewal first |
| 429 | Yes - after Retry-After or backoff |
| 500 | Yes - limited retries with backoff |
| 202 Accepted | Poll 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
Account (API key owner)
└── Project (1 ingest job)
└── Moment (extracted insight, internal)
└── Post (1 LinkedIn draft per moment)Project
| Field | Type | Notes |
|---|---|---|
| id | uuid | Project identifier |
| title | string | Display name you provide on create |
| status | enum | See project statuses below |
| source_type | url | transcript | cloud | device upload (upload) not supported via API in v1 |
| source_material | string | Podcast / Interview or Knowledge & Advisory; set on create |
| professional_perspective | string | Perspective slug (host, guest, full_conversation, expert, advisor, trainer) |
| platforms | string[] | Only linkedin is used today |
| duration_seconds | integer | Billing length; required on create |
| processing_cost_cents | integer | null | Processing time charged for this project, in US cents |
| error_message | string | null | Set when status is failed |
| created_at | ISO 8601 | Creation timestamp |
Project statuses
pending → transcribing → (awaiting_speaker?) → extracting → generating → ready | failedawaiting_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
| Field | Type | Notes |
|---|---|---|
| id | uuid | Post identifier |
| moment_id | uuid | Source moment (read-only) |
| platform | string | linkedin in v1 |
| generated_content | string | Main post body |
| first_comment | string | null | Hashtags, CTA, or extra text |
| status | enum | pending, approved, or discarded |
| publish_tool | string | null | linkedin after publishing |
| image_url | string | null | See Media guide for sentinel values |
| created_at | ISO 8601 | Creation 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_type | Required fields | Notes |
|---|---|---|
| url | source_url, duration_seconds | Public audio/video URL; YouTube supported |
| transcript | transcript, duration_seconds | Full transcript text; skips transcription |
| cloud | provider, file_id, file_name, mime_type, duration_seconds | MP3 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_material | perspective slug | Use when |
|---|---|---|
| Podcast / Interview | host | Only the host's lines; may pause at awaiting_speaker |
| Podcast / Interview | guest | Only the guest's lines; may pause at awaiting_speaker |
| Podcast / Interview | full_conversation | Whole episode or debate-driven posts |
| Knowledge & Advisory | expert | Frameworks, masterclasses, and thought leadership |
| Knowledge & Advisory | advisor | Strategic advice from collaborative calls; may pause at awaiting_speaker |
| Knowledge & Advisory | trainer | Walkthroughs, 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/:idfor moments_found_count vs curated post count.
Tracking progress
- Webhook (recommended): register for project.ready and project.failed
- Polling: GET
/projects/:iduntil status is ready or failed - List: GET
/projectswith 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
- Posts are created with status pending when generation completes.
- You may edit generated_content, first_comment, and image_url via PATCH.
- Set status to approved to mark ready, or discarded to reject.
- 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
| Value | Meaning |
|---|---|
| null | No 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
- POST
.../posts/:postId/generate-image(optional post_content override) - Receive 202 { accepted: true }
- Poll GET post until image_url is a URL or __error__
- 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
- POST
.../posts/:postId/upload-urlwith filename (and optional mime_type, file_size_bytes) - PUT file bytes to upload_url with the returned content_type
- 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
| Approach | Best for |
|---|---|
| Generate | On-brand AI visuals from post copy; no asset prep |
| Upload | Brand 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.