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
- Get a PRO subscription at app.podhoc.com
- Create an API token at app.podhoc.com/account/api-access
- 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:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
urls | string[] | Yes | – | Publicly accessible source URLs |
language | string | No | "en" | Language code (e.g., en-US, es, fr) |
target_duration_minutes | number | No | 10 | Target podcast length (1–120) |
style | string | No | "deep_dive" | Podcast style |
voice_config | object | No | 2 voices | Voice configuration |
tts_model | string | No | "gemini-2.5-flash-tts" | Text-to-speech model |
auto_publish | boolean | No | false | Auto-publish after generation (prod only) |
custom_focus | string | No | null | Custom focus topic (prod only) |
source_weights | object | No | null | Source 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:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
duration_minutes | integer | No | 10 | Target podcast duration |
source_count | integer | No | 1 | Number of content sources |
voice_count | integer | No | 2 | Number of voices |
has_custom_weights | boolean | No | false | Whether 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):
| Duration | Base cost | Test token (Cheaper) | Prod token (Expensive) |
|---|---|---|---|
| 5 min | 30 | 45 | 75 |
| 10 min | 30 | 45 | 75 |
| 30 min | 75 | 113 | 188 |
| 45 min | 113 | 170 | 283 |
| 60 min | 150 | 225 | 375 |
| 90 min | 225 | 338 | 500 |
| 120 min | 300 | 450 | 500 |
Advanced settings impact:
| Setting | Effect | Example |
|---|---|---|
| Multiple sources (>1) | +20 credits | 2 URLs adds 20 to base |
| Custom weights | +10 credits | Enabling source weights adds 10 |
| Multiple voices (>1) | x1.2 multiplier | 2 voices multiplies subtotal by 1.2 |
Credit multipliers:
| Token type | Multiplier | Typical use |
|---|---|---|
Test (phk_test_...) | Cheaper | Development, testing, integration |
Production (phk_prod_...) | Expensive | Production 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 type | Requests/minute | Requests/hour | Concurrent generations |
|---|---|---|---|
| Test | 10 | 60 | 1 |
| Production | 30 | 300 | 5 |
When rate-limited, the response includes a Retry-After header indicating when to retry.
Error codes
| HTTP Status | Code | Description |
|---|---|---|
| 400 | INVALID_REQUEST | Missing required fields or invalid parameters |
| 400 | INVALID_DURATION | Duration outside 1–120 minute range |
| 400 | TEST_TOKEN_RESTRICTED | Feature not available with test tokens |
| 401 | UNAUTHORIZED | Invalid, expired, or revoked API token |
| 402 | INSUFFICIENT_CREDITS | Not enough credits for the request |
| 404 | PODCAST_NOT_FOUND | Podcast not found or not owned by you |
| 429 | RATE_LIMITED | Rate limit exceeded (check Retry-After header) |
| 500 | INTERNAL_ERROR | Unexpected 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
| Feature | Test token | Production token |
|---|---|---|
| Duration limit | 5 minutes max | 120 minutes max |
| URL limit | 1 per request | Unlimited |
| Languages | English only | All 73 languages |
| Voices | Max 2 | All voice options |
| Custom focus | Not available | Available |
| Source weights | Not available | Available |
| File uploads | Not supported | Not supported |
| Auto-publish | Not available | Available |
| Credit multiplier | Cheaper | Expensive |
| Rate limit | 2/min, 20/hr | 30/min, 300/hr |
| Concurrent jobs | 1 | 5 |