Documentation

Build with the
PodNex API.

Generate studio-quality podcasts programmatically, get notified the moment an episode is ready, and integrate PodNex into your own product.

Introduction

The PodNex API lets you turn text content into a natural-sounding, two-voice podcast episode with a single request, and be notified via webhook when it's ready.

All requests are made over HTTPS to the base URL below. All request and response bodies are JSON.

Base URL
https://api.podnex.tech

Authentication

API requests are authenticated with an API key, passed as a bearer token in the Authorization header:

http
Authorization: Bearer pk_live_...

Create a key from your dashboard — Settings → API Keys. The full key is shown once, at creation — store it somewhere safe.

Keys are issued with one or both of the following scopes. Requests made with a key missing the required scope get a 403.

FieldTypeDescription
podcasts:readscopeList and fetch podcasts, check generation status, download finished audio.
podcasts:writescopeCreate, update, delete, and retry podcasts.

API keys can only be created, listed, or revoked from an authenticated dashboard session — not via the API itself, and not with another API key. This means a leaked key can never be used to mint itself broader access. The same applies to managing webhooks.

Quickstart

Generate your first episode:

curl -X POST https://api.podnex.tech/api/v1/podcasts \
  -H "Authorization: Bearer pk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "noteContent": "The history of coffee, from Ethiopian highlands to a global industry — origins, the spread through the Ottoman Empire and Europe, and how it shaped trade routes for centuries.",
    "duration": "SHORT",
    "hostVoice": "Sierra",
    "guestVoice": "Daniel"
  }'

Generation runs asynchronously. Poll GET /api/v1/podcasts/:id/status or register a webhook (see below) to know when it's done, rather than blocking on the request above.

Voices

Every episode is a conversation between a host and a guest voice. Pass any of the names below as hostVoice / guestVoice — both are optional and default to Sierra / Daniel.

Host voices

  • SierraWarm & Friendly
  • HannahProfessional
  • MelodyCasual
  • LaurenAuthoritative
  • EmilyBright
  • KaitlynEnergetic
  • LunaCalm

Guest voices

  • DanielEngaging
  • NoahEnergetic
  • EthanThoughtful
  • JasperCurious
  • CalebCalm
  • RonanDeep
  • ZaneDynamic

You can preview every voice before you build against it on the podcast creation page.

Endpoints

Create a podcast

POST/api/v1/podcastsrequires scope: podcasts:write
FieldTypeDescription
noteContentrequiredstring100–50,000 characters. The source content your episode is generated from.
duration"SHORT" | "LONG"SHORT is 3-5 minutes, LONG is 8-10 minutes. Defaults to "SHORT".
titlestringUp to 200 characters. Left blank, a title is generated from your content.
hostVoice / guestVoicestringSee Voices above. Default to Sierra / Daniel.
webhookUrlstring (url)Optional one-off override — most integrations register a persistent webhook instead (see below).

Returns 201 with the created podcast, status QUEUED.

List podcasts

GET/api/v1/podcastsrequires scope: podcasts:read
FieldTypeDescription
pagenumberDefault 1.
limitnumberDefault 20, max 100.
status"QUEUED" | "PROCESSING" | "COMPLETED" | "FAILED"Filter by status.
sort / order"createdAt" | "updatedAt" / "asc" | "desc"Default createdAt, desc.

Get / check status / download

GET/api/v1/podcasts/:idrequires scope: podcasts:read
GET/api/v1/podcasts/:id/statusrequires scope: podcasts:read
GET/api/v1/podcasts/:id/downloadrequires scope: podcasts:read

/status returns a lightweight { status, progress, currentStep } — cheaper to poll than the full object. /download returns a time-limited, presigned { url } for the finished audio file (also embedded directly in the podcast object once COMPLETED).

Update / delete / retry

PATCH/api/v1/podcasts/:idrequires scope: podcasts:write
DELETE/api/v1/podcasts/:idrequires scope: podcasts:write
POST/api/v1/podcasts/:id/retryrequires scope: podcasts:write

/retry re-queues a FAILED podcast with the same content and settings.

Usage stats

GET/api/v1/podcasts/statsrequires scope: podcasts:read

Returns your all-time and this-month podcast counts and total generated minutes.

Webhooks

Register a webhook from Settings → Webhooks to get notified as a podcast moves through its lifecycle, instead of polling. Webhook management requires a dashboard session — it isn't available via API key.

Events

FieldTypeDescription
PODCAST_CREATEDeventFired immediately once a podcast is queued.
PODCAST_PROCESSINGeventFired when generation actually starts.
PODCAST_COMPLETEDeventFired when audio is ready. Payload includes a presigned, downloadable audioUrl valid for 1 hour.
PODCAST_FAILEDeventFired if generation errors out.

Delivery

Each event is POSTed to your URL as JSON, with these headers:

http
Content-Type: application/json
X-Webhook-Event: PODCAST_COMPLETED
X-Webhook-Timestamp: 1784360230628
X-Webhook-Signature: <hex-encoded HMAC-SHA256 of the raw request body>

Failed deliveries (network error or non-2xx response) are retried up to 3 times with exponential backoff.

Example payload — PODCAST_COMPLETED

json
{
  "podcastId": "cmrq2fh6w000110eyvl8qngx4",
  "status": "COMPLETED",
  "audioUrl": "https://podnex-audio.s3.eu-north-1.amazonaws.com/...(presigned)",
  "audioDuration": 8,
  "timestamp": "2026-07-18T07:49:58.254Z"
}

Verifying signatures

The secret shown when you create a webhook signs every delivery with HMAC-SHA256 over the raw JSON body. Verify it using the raw request body — not a re-serialized object, which can produce a different byte sequence and a signature mismatch.

import crypto from "crypto";

function verifyWebhook(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody) // raw string, not JSON.parse(rawBody)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader),
    Buffer.from(expected)
  );
}

Errors

Every response follows the same envelope:

{ "success": true, "data": { ... } }
FieldTypeDescription
400statusValidation failed — check the error message.
401statusMissing, invalid, or expired API key / session.
403statusValid key, but missing the required scope, or the action requires a dashboard session.
404statusThe resource doesn't exist, or isn't yours.
500statusSomething broke on our end — safe to retry.