Skip to main content

Podhoc API Documentation

Integrate podcast generation into your applications

The Podhoc API lets you programmatically create AI-generated podcasts from publicly accessible URLs. Build podcast generation into your workflows, apps, and platforms.

Content Sources: The API accepts publicly accessible URLs only. File uploads and raw text are not currently supported. If your organisation has compliance or security requirements that prevent public hosting, please create a support ticket to discuss enterprise integration options.

Quick start

  1. Get a PRO subscription at app.podhoc.com
  2. Create an API token at app.podhoc.com/account/api-access
  3. Make your first request using the examples below

Authentication

All API requests require an X-Api-Key header with your API token:

X-Api-Key: phk_test_a1b2c3d4e5f6...

Base URL: https://api-ext.podhoc.com/v1

Token types:

  • Test tokens (phk_test_...) – Cheaper, limited features. For development and testing.
  • Production tokens (phk_prod_...) – Expensive, full access. For production use.

Endpoints

POST /v1/podcasts – Create a podcast

Start generating a new podcast from one or more sources.

Request parameters:

ParameterTypeRequiredDefaultDescription
urlsstring[]YesPublicly accessible source URLs
languagestringNo"en"Language code (e.g., en-US, es, fr)
target_duration_minutesnumberNo10Target podcast length (1–120)
stylestringNo"deep_dive"Podcast style
voice_configobjectNo2 voicesVoice configuration
tts_modelstringNo"gemini-2.5-flash-tts"Text-to-speech model
auto_publishbooleanNofalseAuto-publish after generation (prod only)
custom_focusstringNonullCustom focus topic (prod only)
source_weightsobjectNonullSource weighting (prod only)

Note: Only publicly accessible URLs are supported. File uploads (file_ids) and raw text content (text_content) are not available via the API.

Example request:

curl -X POST https://api-ext.podhoc.com/v1/podcasts \
  -H "X-Api-Key: phk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "urls": ["https://example.com/article"],
    "language": "en-US",
    "target_duration_minutes": 10,
    "style": "conversational"
  }'

Example response (202 Accepted):

{
  "success": true,
  "data": {
    "podcast_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "processing",
    "estimated_duration_minutes": 10,
    "credits_charged": 38
  },
  "meta": {
    "request_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
    "credits_charged": 38,
    "credit_balance": 462
  }
}

GET /v1/podcasts/{id}/status – Poll generation status

Check the progress of a podcast being generated.

Example request:

curl https://api-ext.podhoc.com/v1/podcasts/a1b2c3d4-e5f6-7890-abcd-ef1234567890/status \
  -H "X-Api-Key: phk_test_..."

Example response:

{
  "success": true,
  "data": {
    "podcast_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "completed",
    "progress": 100,
    "started_at": "2026-03-27T10:00:00+00:00"
  }
}

Status values: requested, processing, completed, failed.

GET /v1/podcasts/{id}/download – Download podcast

Get a presigned download URL for a completed podcast. The URL expires after 1 hour.

Example request:

curl https://api-ext.podhoc.com/v1/podcasts/a1b2c3d4-e5f6-7890-abcd-ef1234567890/download \
  -H "X-Api-Key: phk_test_..."

Example response:

{
  "success": true,
  "data": {
    "download_url": "https://s3.amazonaws.com/...",
    "expires_at": "2026-03-27T11:00:00+00:00",
    "format": "mp3",
    "duration_seconds": 600
  }
}

GET /v1/estimate-cost – Estimate credit cost

Preview the credit cost before creating a podcast.

Query parameters:

ParameterTypeRequiredDefaultDescription
duration_minutesintegerNo10Target podcast duration
source_countintegerNo1Number of content sources
voice_countintegerNo2Number of voices
has_custom_weightsbooleanNofalseWhether custom weights are used

Example request:

curl "https://api-ext.podhoc.com/v1/estimate-cost?duration_minutes=30&source_count=2&voice_count=2" \
  -H "X-Api-Key: phk_test_..."

Example response:

{
  "success": true,
  "data": {
    "base_credits": 114,
    "credit_multiplier": 1.5,
    "final_credits": 171,
    "breakdown": {
      "base_cost": 75,
      "multi_source_bonus": 20,
      "custom_weights_bonus": 0,
      "voice_multiplier": 1.2,
      "subtotal_before_cap": 114,
      "cap_applied": null,
      "tier_max_cost": 500
    },
    "formula": "max(30, ceil(30 x 2.5)) + 20 x 1.2 = 114 x multiplier = 171"
  }
}

GET /v1/account/credits – Check credit balance

Example request:

curl https://api-ext.podhoc.com/v1/account/credits \
  -H "X-Api-Key: phk_test_..."

Example response:

{
  "success": true,
  "data": {
    "credits": 1500,
    "tier": "pro"
  }
}

Pricing

Podcast generation uses dynamic credit pricing. The API applies an additional multiplier based on your token type.

Formula:

Base cost:     max(30, ceil(duration_minutes x 2.5))
Bonuses:       + 20 if more than 1 source
               + 10 if custom weights enabled
Voice scaling: x 1.2 if more than 1 voice
Cap:           min(500, tier_max_cost)
API cost:      base_credits x credit_multiplier

Duration-based cost examples (1 source, 1 voice, no custom weights):

DurationBase costTest token (Cheaper)Prod token (Expensive)
5 min304575
10 min304575
30 min75113188
45 min113170283
60 min150225375
90 min225338500
120 min300450500

Advanced settings impact:

SettingEffectExample
Multiple sources (>1)+20 credits2 URLs adds 20 to base
Custom weights+10 creditsEnabling source weights adds 10
Multiple voices (>1)x1.2 multiplier2 voices multiplies subtotal by 1.2

Credit multipliers:

Token typeMultiplierTypical use
Test (phk_test_...)CheaperDevelopment, testing, integration
Production (phk_prod_...)ExpensiveProduction applications

Credit multipliers (Cheaper for test, Expensive for production) are the default tiers. These may be adjusted – the actual multiplier applied to your request is shown in the response metadata.


Code examples

Python – complete workflow

import requests
import time

API_KEY = "phk_prod_your_token_here"
BASE = "https://api-ext.podhoc.com/v1"
HEADERS = {"X-Api-Key": API_KEY, "Content-Type": "application/json"}

# Step 1: Create a podcast
resp = requests.post(f"{BASE}/podcasts", headers=HEADERS, json={
    "urls": ["https://example.com/article"],
    "language": "en-US",
    "target_duration_minutes": 10,
    "style": "conversational",
})
podcast_id = resp.json()["data"]["podcast_id"]
print(f"Created podcast: {podcast_id}")

# Step 2: Poll until complete
while True:
    status_resp = requests.get(
        f"{BASE}/podcasts/{podcast_id}/status",
        headers={"X-Api-Key": API_KEY},
    )
    data = status_resp.json()["data"]
    print(f"Status: {data['status']} ({data['progress']}%)")
    if data["status"] == "completed":
        break
    if data["status"] == "failed":
        raise Exception("Generation failed")
    time.sleep(10)

# Step 3: Download
dl_resp = requests.get(
    f"{BASE}/podcasts/{podcast_id}/download",
    headers={"X-Api-Key": API_KEY},
)
download_url = dl_resp.json()["data"]["download_url"]

audio = requests.get(download_url)
with open("podcast.mp3", "wb") as f:
    f.write(audio.content)
print("Downloaded podcast.mp3")

Node.js – complete workflow

const API_KEY = 'phk_prod_your_token_here';
const BASE = 'https://api-ext.podhoc.com/v1';

async function createAndDownloadPodcast() {
  // Step 1: Create a podcast
  const createRes = await fetch(`${BASE}/podcasts`, {
    method: 'POST',
    headers: { 'X-Api-Key': API_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      urls: ['https://example.com/article'],
      language: 'en-US',
      target_duration_minutes: 10,
      style: 'conversational',
    }),
  });
  const { data: created } = await createRes.json();
  console.log(`Created podcast: ${created.podcast_id}`);

  // Step 2: Poll until complete
  while (true) {
    const statusRes = await fetch(
      `${BASE}/podcasts/${created.podcast_id}/status`,
      { headers: { 'X-Api-Key': API_KEY } },
    );
    const { data: status } = await statusRes.json();
    console.log(`Status: ${status.status} (${status.progress}%)`);
    if (status.status === 'completed') break;
    if (status.status === 'failed') throw new Error('Generation failed');
    await new Promise((r) => setTimeout(r, 10000));
  }

  // Step 3: Download
  const dlRes = await fetch(`${BASE}/podcasts/${created.podcast_id}/download`, {
    headers: { 'X-Api-Key': API_KEY },
  });
  const { data: dl } = await dlRes.json();

  const audioRes = await fetch(dl.download_url);
  const fs = await import('fs');
  const buffer = Buffer.from(await audioRes.arrayBuffer());
  fs.writeFileSync('podcast.mp3', buffer);
  console.log('Downloaded podcast.mp3');
}

createAndDownloadPodcast();

curl – quick reference

# Check credit balance
curl https://api-ext.podhoc.com/v1/account/credits \
  -H "X-Api-Key: $API_KEY"

# Estimate cost
curl "https://api-ext.podhoc.com/v1/estimate-cost?duration_minutes=30&source_count=1&voice_count=2" \
  -H "X-Api-Key: $API_KEY"

# Create podcast
curl -X POST https://api-ext.podhoc.com/v1/podcasts \
  -H "X-Api-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.com/article"], "target_duration_minutes": 10}'

# Poll status
curl https://api-ext.podhoc.com/v1/podcasts/<podcast_id>/status \
  -H "X-Api-Key: $API_KEY"

# Download
curl https://api-ext.podhoc.com/v1/podcasts/<podcast_id>/download \
  -H "X-Api-Key: $API_KEY"

Rate limits

Token typeRequests/minuteRequests/hourConcurrent generations
Test10601
Production303005

When rate-limited, the response includes a Retry-After header indicating when to retry.


Error codes

HTTP StatusCodeDescription
400INVALID_REQUESTMissing required fields or invalid parameters
400INVALID_DURATIONDuration outside 1–120 minute range
400TEST_TOKEN_RESTRICTEDFeature not available with test tokens
401UNAUTHORIZEDInvalid, expired, or revoked API token
402INSUFFICIENT_CREDITSNot enough credits for the request
404PODCAST_NOT_FOUNDPodcast not found or not owned by you
429RATE_LIMITEDRate limit exceeded (check Retry-After header)
500INTERNAL_ERRORUnexpected server error

Error response format:

{
  "success": false,
  "error": {
    "code": "INSUFFICIENT_CREDITS",
    "message": "Your account has 20 credits but this request requires 113 credits.",
    "details": { "required": 113, "balance": 20 }
  },
  "meta": { "request_id": "uuid" }
}

Test vs production tokens

FeatureTest tokenProduction token
Duration limit5 minutes max120 minutes max
URL limit1 per requestUnlimited
LanguagesEnglish onlyAll 73 languages
VoicesMax 2All voice options
Custom focusNot availableAvailable
Source weightsNot availableAvailable
File uploadsNot supportedNot supported
Auto-publishNot availableAvailable
Credit multiplierCheaperExpensive
Rate limit2/min, 20/hr30/min, 300/hr
Concurrent jobs15