setup-guides

Podcast account setup — Apple Podcasts Connect + Spotify for Podcasters

One-time setup for submitting the Graphene podcast feeds to the two directories that drive 80%+ of audio-fiction discovery. Read this start-to-finish before opening either portal — half the questions Apple/Spotify ask are answered by values that already live in code, and getting them wrong post-submission is annoying to undo.

Time: ~90 minutes total, mostly waiting on art generation. Active form-filling is ~20 minutes per platform.

Repeatability: The network-level setup is one-time. Per-show submissions are repeated for each published show (~30 minutes for the first, ~10 minutes for each subsequent one — see the Phase 0 launch checklist for the per-show flow).


0. Before you start

Three decisions to make BEFORE opening either portal. Changing them after Apple verifies is painful, so settle them now.

0a. Cover art (REQUIRED — Apple rejects below 1400×1400)

The canonical network cover currently points at https://www.hivejournal.com/icon.png (the 32×32 bee favicon) — that's a placeholder per the comment in apps/backend/src/constants/graphene.ts. Apple will reject the submission with Invalid cover art (must be 1400×1400 minimum, 3000×3000 recommended).

What you need:

  • 3000×3000 PNG or JPG, sRGB color space
  • No transparent backgrounds (Apple flattens to white, looks bad)
  • Show the network name "Graphene" or the logo prominently — this is what appears in directory search results at thumbnail size
  • File size under 500 KB ideal, hard cap 3 MB

Where it needs to live:

  • Public HTTPS URL (no auth, no redirect chain)
  • Best path: upload to Supabase storage in the public/ bucket, get the public URL, point at it
  • Or: drop it in apps/frontend/public/graphene-cover-3000.png and reference https://www.hivejournal.com/graphene-cover-3000.png

After uploading, update apps/backend/src/constants/graphene.ts:

coverImageUrl: 'https://www.hivejournal.com/graphene-cover-3000.png',
//                                          ^^^^^^^^^^^^^^^^^^^^^^^^^^
//                                          your new URL

Push, deploy, verify by hitting https://www.hivejournal.com/api/podcast/rss and confirming the <itunes:image> tag points at the new URL.

0b. Owner email — brand vs personal

The canonical owner email is currently sandonjurowski@gmail.com (set in graphene.ts). This:

  • Is the email Apple/Spotify use for ownership verification (they email you a code)
  • Appears publicly on the Apple Podcasts listing (under "Contact")

If you want the public-facing email to be a brand address (hello@graphene.fm, podcast@graphene.fm, etc.):

  1. Set up the address FIRST — point the MX record to your mail provider (Google Workspace, Fastmail, Resend Inbound, etc.) and confirm you can receive mail at it
  2. Update ownerEmail in graphene.ts → push, deploy
  3. Hit https://www.hivejournal.com/api/podcast/rss and confirm the <itunes:owner><email> tag updated
  4. THEN submit to Apple/Spotify

Why this order matters: Apple sends the verification code to whatever email is in the RSS at submission time. If you submit with one email and change it after, you'll need to repeat the verification dance (and Apple's UI for that is bad).

0c. Apple ID / Spotify account choice

Both Apple Podcasts Connect and Spotify for Podcasters tie podcasts to an account. You have three choices:

OptionProsCons
Personal Apple ID / SpotifyEasiest — already existsBound to your personal name, harder to hand off to a co-founder later
Brand-dedicated Apple ID (podcast@graphene.fm + new Apple ID)Clean separation, transferableNew Apple ID = no purchase history, occasional "verify this is you" prompts
Apple Business Connect / Spotify for Podcasters team accountMulti-user, audit logMore setup, may require business verification

Recommendation for Phase 0: use the personal Apple ID + personal Spotify. Switching to a brand account later requires re-submitting the show, which Apple handles via the "transfer ownership" flow but takes ~2 weeks. Not worth blocking Phase 0 on it.


1. Network-level metadata (source of truth)

All these values flow from apps/backend/src/constants/graphene.ts into the RSS feed at /api/podcast/rss. You don't type these into Apple/Spotify forms — they're auto-populated when the portal validates your RSS URL. They're listed here so you can verify the portal's auto-pick matches.

FieldCurrent valueApple portal label
Show nameGraphene"Show Name" / <title>
AuthorGraphene"Author" / <itunes:author>
Owner nameGraphene Network"Show Owner"
Owner emailsandonjurowski@gmail.com"Contact Email" / <itunes:owner><email>
Languageen-us"Language" / <language>
CategoryFiction"Category"
SubcategoryDrama"Sub-Category"
TaglineWritten. Spoken. Yours.(appears in <itunes:subtitle>)
Description(400 chars — see graphene.ts)"Description" / <description>
Cover art⚠️ needs replacement per 0a<itunes:image>
ExplicitNo (default)"Explicit Content"
Show typeEpisodic"Type" — Episodic, since chapters can be enjoyed standalone-ish

If any of these need to change long-term, edit the constant + redeploy. The RSS regenerates on every fetch, so the change flows to Apple/Spotify on their next crawl (which Apple does daily for existing feeds). See section 8 for exactly how episodes enter/leave the feed after submission and how many publish at once.


2. Apple Podcasts Connect submission

2.1 Network feed (one time)

The network feed at /api/podcast/rss mixes all chapters from all shows into one listing called "Graphene." Submit this once — it's the discovery anchor for the brand.

  1. Confirm cover art is fixed per 0a.
  2. Open podcastsconnect.apple.com → sign in with the Apple ID chosen in 0c.
  3. Click + (top-left) → New Show.
  4. Choose Add a show with an RSS feed.
  5. Paste: https://www.graphene.fm/api/podcast/rss — proxied via a Next.js rewrite in apps/frontend/next.config.js to the Railway backend's /api/podcast/rss. Brand-correct URL Apple/listeners see is graphene.fm, never the Railway preview URL.
  6. Click Add.
  7. Apple validates the feed (5–60 seconds). Possible outcomes:
    • Valid + auto-populated: the form fills in name, description, category, etc from the feed. Verify they match the table in section 1. Click Submit.
    • Validation errors: see "Common Apple rejections" below. Fix the underlying RSS issue (probably in apps/backend/src/services/season-distribution.ts or the constants file) → redeploy → re-validate.
  8. Apple emails the verification code to the <itunes:owner><email> address. Enter it.
  9. Wait 5–7 business days for the listing to go live. Apple emails when it's approved OR if it was rejected.

2.2 Per-show feeds (repeat per published show)

After the network feed is live, submit each individual show separately at /api/podcast/rss/show/<season-id>. Per-show feeds rank independently for genre search and are how listeners find specific shows vs the umbrella brand. Use /dashboard/admin/podcast-feed to track which shows are pending — see the Phase 0 launch checklist section 1 for the step-by-step.


3. Spotify for Podcasters submission

3.1 Network feed

  1. Open podcasters.spotify.com/pod/dashboard/podcast/get-started → sign in.
  2. Choose I already have a podcast (you do — you've got the RSS).
  3. Paste the network RSS URL: https://www.graphene.fm/api/podcast/rss
  4. Click Next.
  5. Spotify validates (faster than Apple — usually <30 seconds).
  6. Ownership verification: Spotify emails a code to the <itunes:owner><email> address. Enter it.
  7. Confirm metadata — category, language, explicit flag. Spotify auto-fills from the RSS.
  8. Submit. Listing typically appears in 1–3 days; you get an email when it's live.

3.2 Per-show feeds

Spotify accepts the same per-show RSS pattern as Apple. Same flow as 3.1 but with the per-show URL from the share kit or the podcast-feed admin page.

Note: Spotify shows have a "claim podcast" flow if someone else already submitted — for fresh submissions this doesn't apply, but if you see "this feed is already claimed" you'll need to use Spotify's transfer flow (slow, support-ticket-driven).


4. Common Apple rejections + how to fix

Apple's validator is strict. The most-seen failures:

RejectionCauseFix
Invalid cover art (must be at least 1400×1400)Cover URL points at the small bee faviconSection 0a — generate + upload a 3000×3000 PNG, update coverImageUrl
Missing <itunes:owner>Owner block wasn't includedAlready in the RSS — check the feed itself with curl https://.../api/podcast/rss and verify the <itunes:owner> tag is present
Invalid <itunes:image> hrefURL has query params, redirects, or returns non-200Use a clean public URL with no query string, served directly
Episode <enclosure length> is 0An episode has no audio_url set, or audio_url returns 0-byte contentCheck the offending episode in story_episodeschapter_audio_url should be populated. If it's a half-rendered chapter, either render its audio or untrigger it
Invalid RFC-822 pubDate<pubDate> format is wrong (rare — the builder uses correct format)Check apps/backend/src/services/season-distribution.tsformatPubDate helper
Episode duration mismatchSpotify expects non-zero <itunes:duration> on every episodeCheck story_seasons.audio_duration_seconds or story_episodes.chapter_audio_duration_seconds is populated

If Apple rejects, use the in-house feed validator at /dashboard/admin/podcast-feed — click ↻ Re-run to see Apple-relevant issues with severity, location, and a recommendation per finding.


5. Verifying it worked

After Apple approval (5–7 business days)

  1. Search the show title in the Podcasts app on iOS or macOS — listing appears
  2. Tap → confirm the cover art, description, episode list render correctly
  3. Apple Podcasts Connect inbox: approval email (also flags any post-publish issues like a 404 on a recent episode's audio_url)

After Spotify approval (1–3 days)

  1. Search the show title on Spotify mobile or web
  2. Open the show page → confirm the metadata + most recent episodes are crawled

Both platforms

  • New chapters published after submission auto-appear within a few hours (Apple crawls daily, Spotify ~hourly)
  • The "I just published a new chapter, where is it?" question is usually answered by checking the RSS first — if /api/podcast/rss/show/<id> includes the new chapter, it's just a crawl delay, not a feed bug

6. Changing things after submission

Metadata change (name, description, category, cover art)

Edit apps/backend/src/constants/graphene.ts → push → deploy. Apple/Spotify crawl the change on their next pass (Apple: daily, Spotify: ~hourly).

Don't change metadata in the Apple/Spotify portal directly — the next crawl will overwrite your portal edit with the RSS values. Always edit the source of truth.

Exception: cover art uploaded via the portal directly will sometimes override the RSS <itunes:image> — Apple's behavior here is inconsistent. Safer to host the cover at a stable URL and edit the constant.

Owner email change

Tricky. Apple/Spotify verified the OLD email. Changing the email in the RSS will:

  • Apple: trigger a re-verification flow within 30 days
  • Spotify: usually re-verifies silently

If you change the email, watch your old inbox for the next 30 days for any verification request you might miss in the new inbox.

Per-show iTunes metadata (subtitle, category, keywords)

Already settable per-show via the itunes_* columns on story_seasons (migration 266). The per-show RSS feed picks these up automatically. Recommended values for the 8 priority shows live in docs/marketing/2026-05-19-graphene-apple-podcasts-aso-sweep.md — apply via APPLY_ASO_SWEEP.sql (already applied to prod as of 2026-05-24).

Removing or replacing a show

If a show needs to be unpublished from the directories (rare):

  1. Set story_seasons.is_published_to_graphene = false — the per-show feed at /api/podcast/rss/show/<id> returns an empty feed
  2. Email Apple/Spotify support (links in their respective portals) to request takedown
  3. Mark the show as unsubmitted on /dashboard/admin/podcast-feed so it doesn't surface as "pending" in the tracker

Apple/Spotify don't auto-delete from a feed going empty — they just stop adding new episodes. Takedown is a manual support request.


7. Quick reference — URLs you'll need

URLWhat it is
https://podcastsconnect.apple.com/Apple Podcasts Connect dashboard
https://podcasters.spotify.com/pod/dashboard/podcast/get-startedSpotify for Podcasters "add show" entry point
https://www.graphene.fm/api/podcast/rssGraphene network RSS feed
https://www.graphene.fm/api/podcast/rss/show/<season-id>Per-show RSS feed
/dashboard/admin/podcast-feedIn-house feed validator + per-show submission tracker
https://castfeedvalidator.com/Third-party RSS validator (for second opinion)
https://podba.se/validateAnother third-party validator

8. How the feed stays current after submission (live-feed mechanics)

Once a feed is submitted, you never "publish episodes to Spotify/Apple" one at a time. The RSS feed is a live view of the database, regenerated on every fetch — Apple re-crawls roughly daily, Spotify roughly hourly, and each crawl ingests whatever the feed currently contains. New episodes appear automatically; nothing is pushed.

What's in the feed at any given crawl

An episode <item> is included only when all three hold (enforced in season-distribution.ts, the novel-mode chapter query):

  1. Audio is rendered — the chapter has a chapter_audio_url.
  2. The show is published to the podcast — its season_publishings.podcast_published_at is set (the 🎧 Podcast button on the season page). This is the master gate: flip it on once for a show and every rendered chapter of that show flows into the feed automatically, now and going forward.
  3. It's still in its free window (public feeds only) — chapter_free_until IS NULL (always free) OR chapter_free_until > now. Chapters past their free window drop out of the public network + per-show feeds. The Graphene+ subscriber feed (/api/podcast/rss/subscriber/:token) skips this filter and always carries the full back catalog.

Journal-mode seasons contribute one item each (the compiled season MP3); novel-mode shows contribute one item per chapter.

How many episodes publish at once

  • Hard cap: 500 items, ordered newest-rendered-first (.order('chapter_audio_rendered_at', desc).limit(500)). That's a ceiling on the entire feed, not a per-crawl batch.
  • First submission: Apple/Spotify ingest everything currently in the feed in one pass — every published show's still-free chapters, up to the 500 cap.
  • Ongoing crawls: the platform picks up whatever is new since its last crawl (usually just the chapters rendered since) and drops anything that fell out of its free window.
  • Latency: the feed is CDN-cached for 5 minutes (Cache-Control: max-age=300), so a freshly rendered chapter is eligible the moment the cache expires + the platform's next crawl runs.

Network feed vs per-show feeds

  • Network feed (/api/podcast/rss) = the "Graphene" show — all chapters from all published shows, mixed into one listing. This is the brand discovery anchor.
  • Per-show feeds (/api/podcast/rss/show/<season-id>) = each show as its own Apple/Spotify listing, ranking independently for genre search.

Both obey the same three gating conditions + the 500-item cap above.

Practical gotcha — the public feed is a rolling window

Because the free-window filter drops chapters after they expire, a listener browsing the public Graphene show on Spotify sees recent + still-free chapters, not the complete archive. The full catalog lives only behind the Graphene+ subscriber feed. If you want a show's whole back catalog permanently visible on the public feed, set those chapters' chapter_free_until to NULL (always free) or widen the free window.


PODCAST ACCOUNT SETUP — Docs | HiveJournal