dvmkitdocs
DVM Reference

cast

Reference for the cast DVM, which hosts private RSS feeds and podcast episodes, with six capabilities, retention pricing, and error codes.

cast hosts private RSS feeds and podcast episodes. Publish audio into a feed and cast returns a public RSS 2.0 + iTunes XML URL that any podcast client can subscribe to. No object store, no RSS host, no separate accounts. It is dvmkit's first stateful-resource DVM: feeds are owned by a caller pubkey, and every capability is signed with that key.

Canonical endpoint: https://cast.dvmkit.ai. Six capabilities under one descriptor, all owner-signed with secp256k1 + BIP-340 Schnorr requests.

Published reference. This page is the source of truth for cast's documented inputs, outputs, prices, and errors. The running service publishes its current capability schemas and advertised prices in its live descriptor. Full output envelope shapes are in the dvm CLI reference.

Capabilities

The SDK dispatches on the wire capability field. The first add-episode against a feed_slug creates the feed; every later call requires the calling pubkey to match the feed owner.

CapabilityPricingIdempotentWhat it does
add-episodePaidNoPublish an episode (creates the feed on first call)
update-feedFreeYesEdit feed-level metadata + cover art (post-publish)
update-episodeFreeYesPatch an episode in place, preserving its GUID
delete-episodeFreeYesRemove one episode by episode_id
delete-feedFreeYesRemove a feed and cascade-delete its episodes
list-feedsFreeYesList feeds owned by the calling pubkey (recovery path)

add-episode

Submit a signed AddEpisodeInput with one of two mutually-exclusive audio sources:

  • audio_url: cast HEAD-fetches the URL during the quote, prices on size × TTL, then streams it into storage after payment.
  • audio_upload_handle: UUID from a prior POST /v1/ingest/audio (bytes-upload); the quote prices from the recorded byte count.

ttl_days is required (no default): it sets the retention window and drives storage cost. At expiry the cleanup cron auto-deletes the episode; restoring means re-publishing (and paying again). Because it's consequential and hard to reverse, an agent acting for a human should confirm the retention window before publishing.

Result: { feed_url, episode_id, ttl_days, expires_at }.

Feed-level title, description, author, category, explicit flag, and cover art are not set here: use update-feed. Only per-episode artwork (episode_image_url / episode_image_upload_handle), chapters, and transcript belong on the episode. Caps: 200 MB per episode, 50 episodes per feed, ttl_days ∈ [1, 3650].

Free capabilities

  • update-feed: edit any feed metadata field post-publish (title, description, language, author, category, explicit, owner name/email, copyright, iTunes type, cover image). Feed identity/URL is preserved.
  • update-episode: patch an episode's title, description, artwork, transcript, or chapters in place. Preserves GUID, pub_date, feed position, and audio enclosure (podcast clients see an edit, not a re-download). Replace-only: swapping audio is delete-episode + re-add.
  • delete-episode / delete-feed: idempotent removes; a missing target returns { deleted: false }.
  • list-feeds: the recovery path for a restarted agent. Returns every feed owned by the calling pubkey with canonical feed_url values, episode counts, and an empty_feed warning when a feed has no episodes (Apple rejects those).

CLI example

Every capability is invoked the same way: dvm request -d dvmkit--cast/<capability> with a signed identity.

dvm request -d dvmkit--cast/add-episode --as my-identity --data '{
  "feed_slug":"my-show","feed_title":"My Show",
  "episode":{"title":"Ep 1","audio_url":"https://example.com/ep1.mp3","ttl_days":365}
}'
dvm request -d dvmkit--cast/list-feeds     --as my-identity --data '{}'
dvm request -d dvmkit--cast/update-feed    --as my-identity --data '{"feed_slug":"my-show","feed":{"author":"Jane Smith"}}'
dvm request -d dvmkit--cast/delete-episode --as my-identity --data '{"feed_slug":"my-show","episode_id":"<uuid>"}'

Pricing

Only add-episode is paid:

ComponentCost
Per-add fee$0.02 USD
Storageceil(audio_bytes / 1MB) × ttl_days × $0.000005 USD

Storage scales with the retention window, so a longer ttl_days costs more. Sats binding is derived at the 402 handshake using the SDK FX fetcher; both the /v1/quote description and the add-episode result name the retention window and expiry instant explicitly.

Public surfaces

  • GET https://cast.dvmkit.ai/feeds/{owner_short}/{feed_slug}: the public RSS 2.0 + iTunes feed. owner_short is the first 12 hex chars of sha256(pubkey).
  • POST /v1/ingest/audio: stage raw audio bytes (signed claim), returns a 15-min upload_handle.
  • POST /v1/ingest/image: stage cover / episode artwork (JPEG/PNG/WebP, square, ≥ 1400×1400).

Auth

Per-request secp256k1 + BIP-340 Schnorr signature over the canonical-JSON body, at the descriptor level so all six capabilities share one auth slot. Drift window ±5 minutes; replay window 10 minutes (Postgres-backed, survives restarts). An unsigned, stale or replayed request rejects with a structured 401 auth_error before payment is taken.

The CLI handles this for you: dvm init creates a default signing identity and dvm quote / dvm request sign with it automatically. Pass --as <identity> only when you want to sign as a different one. For cast that also selects which owner's feeds you are operating on, since the verified pubkey is the ownership key.

Prepaid credit

cast offers a prepaid balance, so an agent publishing a run of episodes funds once instead of paying per job. The funding menu rides on every quote and every 402.

FieldValue
Minimum funding$0.10
Maximum residual balance$5.00
Credit lifetime30 days from the most recent funding
dvm credit fund dvmkit--cast --amount 2.00
dvm credit balance dvmkit--cast
dvm credit drain dvmkit--cast     # reclaim it, works after expiry too

A job priced above the maximum still clears: the ceiling binds how much balance you may hold, not what you may spend. And a failed job never takes your money: the charge is released back onto your credit rather than settled. See Prepaid credit for the full model. (This is about job payments; deleting a feed or episode does not refund the storage you already used.)

Error codes

Failures throw a typed error rendered as code (+ sub_reason). payment_failed comes from the SDK's 402 handshake before any cast handler runs.

CodeRepresentative sub-reasonsTrigger
auth_errorsignature_invalid, timestamp_drift, replay_detectedRequest signature, clock, or nonce invalid
invalid_inputssrf_blocked, wrong_content_type, oversize, probe_failed, cover_invalid, audio_source_required, upload-handle not_found / expired / not_owner / kind_mismatchBad source URL, bad asset, or a bad/expired upload handle
feed_full—Feed already at 50 episodes. Delete one or use a new feed_slug
provider_errorupstream_status, upstream_timeout, upstream_network, upload_failedThe upstream source or storage host failed, not cast itself. Transient, retry
rate_limitedupstream, ingest_concurrency, ingest_ip_concurrency, ingest_global_concurrencyUpstream 429 or a concurrent-upload cap. Back off per Retry-After
not_foundfeed_not_found, episode_not_found, path_not_foundOperating on a feed/episode the caller doesn't own or that doesn't exist
internal_errorunexpectedAn unexpected DVM-side fault

See also

On this page