VoiceTopics Integration Guide

Overview

VoiceTopics provides a RESTful API for integrating text-to-speech audio generation into your content management system, website, or application. This guide covers authentication, available endpoints, and best practices for integration.

Table of Contents

Authentication

Creating Access Keys

Access keys are required to authenticate API requests. Organization administrators can create and manage access keys through the VoiceTopics web interface:

  1. Log in to your VoiceTopics account
  2. Navigate to Settings → Access Keys
  3. Click Create Access Key
  4. Provide a descriptive name (e.g., "WordPress Plugin", "Mobile App")
  5. Copy both the Access Key ID and Secret Key

Important: The secret key is only shown once during creation. Store it securely - you won't be able to retrieve it again.

Access Key Properties

  • Access Key ID: Starts with vt_ - used for identification
  • Secret Key: Starts with sec_ - used for authentication
  • Status: ACTIVE or INACTIVE - only active keys can authenticate
  • Name: Your custom label for the key

Using Access Keys

All API requests must include an Authorization header with the Bearer token format:

Authorization: Bearer <your-secret-key>

Example:

Authorization: Bearer sec_abc123def456ghi789

The secret key identifies your organization and authenticates the request. Do not share your secret key publicly or commit it to version control.

Using External IDs

External IDs allow you to track VoiceTopics content using your own identifiers (UUIDs, content hashes, etc.), enabling seamless synchronization and preventing duplicate content creation.

What are External IDs?

An external ID is an optional identifier you provide when creating or updating content through the integration API. Once set, you can use this ID to:

  • Update content without storing VoiceTopics content IDs
  • Prevent duplicate content creation across API calls
  • Reference content using identifiers from your own system

Format Restrictions

External IDs must not start with the following reserved prefixes:

  • org- (organization IDs)
  • con- (content IDs)
  • g- (group IDs)

Valid examples:

  • 550e8400-e29b-41d4-a716-446655440000 (UUID)
  • sha256:abc123def456... (content hash)
  • my-blog-post-123 (custom identifier)
  • 2024-01-15-article-title (date-based identifier)

Invalid examples:

  • org-12345 ❌ (reserved prefix)
  • con-abc123 ❌ (reserved prefix)
  • g-xyz789 ❌ (reserved prefix)

Global Uniqueness

Important: External IDs are globally unique across all organizations in VoiceTopics. If you attempt to create content with an external ID that already exists in another organization, the API will return a 409 Conflict error.

Security Best Practice: Hash Salting

Since external IDs are globally unique, other organizations could potentially discover your content URLs if you use simple URL hashes. To prevent this, salt your hashes with a secret value:

const crypto = require('crypto');

// Without salt (INSECURE - others can guess your URLs)
const insecureId = crypto.createHash('sha256').update(articleUrl).digest('hex');

// With salt (SECURE - others cannot guess your URLs)
const SECRET_SALT = process.env.VOICETOPICS_SALT; // Store securely
const secureId = crypto.createHash('sha256')
  .update(articleUrl + SECRET_SALT)
  .digest('hex');

By salting your external IDs, you prevent other organizations from: - Discovering your unpublished content URLs - Enumerating your content systematically - Learning about your content structure

Upsert Behavior

External IDs enable automatic upsert operations (create or update):

  1. Create: If external ID doesn't exist, new content is created
  2. Update: If external ID exists in your org, content is updated
  3. Collision Prevention: If external ID exists in another org, API returns error

Code Examples

Create Content with External ID

curl -X POST https://voicetopics.xyz/api/integration/notify \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sec_your_secret_key" \
  -d '{
    "externalId": "550e8400-e29b-41d4-a716-446655440000",
    "url": "https://example.com/my-article",
    "title": "My Article Title",
    "content": "Article content goes here...",
    "author": "Jane Doe",
    "publishDate": "2025-01-15"
  }'

Update Content Using External ID (Upsert)

# Same call - automatically updates if externalId exists
curl -X POST https://voicetopics.xyz/api/integration/notify \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sec_your_secret_key" \
  -d '{
    "externalId": "550e8400-e29b-41d4-a716-446655440000",
    "title": "Updated Article Title",
    "content": "Updated content...",
    "publishDate": "2025-01-16"
  }'

Update with Both IDs (Explicit Update)

curl -X POST https://voicetopics.xyz/api/integration/notify \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sec_your_secret_key" \
  -d '{
    "id": "con-abc123",
    "externalId": "550e8400-e29b-41d4-a716-446655440000",
    "title": "Updated Title",
    "content": "Updated content..."
  }'

Check Status by External ID

curl https://voicetopics.xyz/api/integration/content/550e8400-e29b-41d4-a716-446655440000/status \
  -H "Authorization: Bearer sec_your_secret_key"

API Endpoints

Base URL

  • Production: https://voicetopics.xyz
  • Staging: Contact VoiceTopics support for staging access

Other Expected Headers

Requests should also specify:

Content-Type: application/json

Create or Update Content

curl -X POST https://voicetopics.xyz/api/integration/notify \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sec_your_secret_key" \
  -d '{
    "id": "con_abc123",
    "url": "https://example.com/my-article",
    "title": "Updated Article Title",
    "content": "This is the updated content.",
    "author": "Jane Doe",
    "publisher": "My Website",
    "publishDate": "2025-11-05",
    "group": "Updated Category"
  }'

Creating New Content

To create new content, send a request with no id parameter, and at least one of url or content specified. The response will include the new id which you should store for future updates.

To update existing content, send a request with id specified, and content or other metadata fields to update.

Endpoint: POST /api/integration/notify

Request Body:

{
  "url": "https://example.com/article",
  "title": "Article Title",
  "content": "This is the full text of the article, so it can be quite long ...",
  "id": "con_abc123",
  "author": "Jane Doe",
  "publisher": "My Website",
  "publishDate": "2025-11-05",
  "group": "Blog Posts"
}

Note on Groups: If you specify a group name that doesn't exist, VoiceTopics will automatically create it for your organization. This allows you to organize content into categories without pre-creating groups through the web interface.

Parameters:

  • url (string, required*): The canonical URL of the content
  • content (string, required*): The text to convert to audio
  • title (string, optional): The title of the content
  • id (string, optional): VoiceTopics content ID for updates
  • externalId (string, optional): Your own identifier for this content (see Using External IDs)
  • author (string, optional): The author's name for metadata
  • publisher (string, optional): The publisher or organization name for metadata
  • publishDate (string, optional): Publication date in YYYY-MM-DD format (defaults to today)
  • group (string, optional): Group/category name - creates the group if it doesn't exist

Response:

{
  "id": "con-967a83f6cd43",
  "organization": "org-50fe4f0c04bf",
  "publishDate": "2025-11-02",
  "group": "",
  "currentVersionData": {
    "audio": {
      "id": "audio-151ac6463bef",
      "uri": "https://voice-topics-audio-bucket-prod.s3.us-east-1.amazonaws.com/audio-151ac6463bef.mp3?...",
      "duration": 27.141,
      "sectionIndex": 0
    },
    "sections": null,
    "metadata": {
      "title": "Article Title",
      "author": "Author Name",
      "publisher": "My Website"
    },
    "status": "RECORDED"
  }
}

Key Response Fields:

  • id: Unique content identifier (format: con-{hash}) - Store this for future updates
  • organization: Organization ID this content belongs to
  • publishDate: Date content was published (YYYY-MM-DD format)
  • group: Optional content group/category
  • currentVersionData: Audio version information:
  • status: Audio generation status (PENDING, TRANSCRIBING, RECORDING, RECORDED)
  • metadata: Content metadata
    • title: Content title
    • author: Content author (optional)
    • publisher: Publisher name (optional)
  • audio: Combined audio file (no ad breaks)
    • id: Audio file identifier
    • uri: Signed S3 URL to audio file (expires after 1 hour)
    • duration: Audio duration in seconds
    • sectionIndex: Section number (0 for single audio)
  • sections: Array of audio sections (separated by ad breaks; only populated if ad breaks are present)
    • id: Audio file identifier
    • uri: Signed S3 URL to audio file (expires after 1 hour)
    • duration: Audio duration in seconds
    • sectionIndex: Section number (0 for single audio)

Important Notes:

  • Save the id: Store the returned content ID in your database to enable future updates
  • Signed URLs: Audio URIs are signed S3 URLs that expire after 1 hour.
  • Status Checking: Use the dedicated status endpoint to monitor processing progress

Error Responses:

  • 400 Bad Request - Missing required fields, invalid data, or invalid external ID format
  • 401 Unauthorized - Invalid or missing access token
  • 403 Forbidden - Content belongs to different organization
  • 404 Not Found - ContentId provided but content doesn't exist
  • 409 Conflict - External ID collision (already exists in another organization or ID mismatch)
  • 412 Precondition Failed - No active subscription
  • 429 Too Many Requests - Usage limit reached
  • 500 Internal Server Error - Server error

Check Content Status

Audio generation is asynchronous. Clients can poll the status endpoint to check when processing is complete.

Endpoint: GET /api/integration/content/:id/status

Response:

{
  "id": "con-967a83f6cd43",
  "status": "COMPLETED",
  "publishStatus": "PUBLISHED",
  "creationStatus": "COMPLETED",
  "versionStatus": "RECORDED",
  "publishWhenReady": true
}

Status Values:

  • status: Overall content status
  • PENDING - Awaiting processing
  • PROCESSING - Currently generating audio
  • COMPLETED - Audio generation complete
  • FAILED - Processing failed

  • publishStatus: Publication state

  • UNPUBLISHED - Not yet published
  • PUBLISHED - Available publicly

  • creationStatus: Workflow stage

  • DRAFT - In draft mode
  • COMPLETED - Finalized and ready

  • versionStatus: Audio generation status

  • PENDING - Not started
  • TRANSCRIBING - Processing text
  • RECORDING - Generating audio
  • RECORDED - Audio ready and available

Error Responses:

  • 401 Unauthorized - Invalid or missing access token
  • 403 Forbidden - Content belongs to different organization
  • 404 Not Found - Content ID doesn't exist
  • 500 Internal Server Error - Server error

Support

For questions, issues, or feature requests: