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
API keys are not JWTs. Do not send sk_sparkvox_ tokens to /api/developer/* or Supabase auth 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 credits when creating a project | Top up credits in the SparkVox app; 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 | More than 200 requests in 24h 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' or 'transcript'. File upload is not supported in v1." }HTTP/1.1 402 Payment Required
Content-Type: application/json
{ "error": "Insufficient credits." }Rate limit errors (429)
Each API key allows 200 requests 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
| error | Meaning |
|---|---|
| insufficient_credits | Account could not cover processing cost |
| 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 - add credits 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 | upload not supported via API in v1 |
| platforms | string[] | Only linkedin is used today |
| duration_seconds | integer | Billing length; required on create |
| processing_cost_cents | integer | null | Charged credits in cents |
| error_message | string | null | Set when status is failed |
| created_at | ISO 8601 | Creation timestamp |
Project statuses
pending → transcribing → extracting → generating → ready | failedPoll 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, buffer, or publer after scheduling |
| 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 piece of long-form content you want repurposed into LinkedIn posts. 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 |
File upload (source_type: upload) is not available via the API in v1. Use url or transcript, or create uploads in the SparkVox app.
Perspective
Controls how the transcript is interpreted when writing posts:
| Value | Use when |
|---|---|
| show_brand | Host-led content; default for url and forced for transcript |
| thought_leader | You are a guest on someone else's show |
| expert_guest | Guest appearance; same speaker-selection behavior as thought_leader |
Billing
duration_seconds is required on create and drives credit usage (per minute, rounded up). Moment extraction and post generation are included. Failed projects may still consume credits depending on pipeline stage.
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
- 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 on PATCH to schedule to LinkedIn, Buffer, or Publer.
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, buffer, or publer) requires that integration to be connected 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. Future API versions may add format types when the product ships Phase 10.
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 in project processing credits for sprout-tree usage in the app; API generation uses the same pipeline.
Full endpoint reference. Generate image for details.
Custom upload
- POST .../posts/:postId/upload-url with 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. 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 or raw transcript text. Direct file upload for project source is not available on the API in v1. Raw audio from URL projects is stored temporarily during processing and removed after transcription when applicable.