PodcastsToText
Getting Started

Authentication and keys

How API keys work, which header to send, what each scope grants, and how to rotate one.

Every request carries an API key as a bearer token. There is no OAuth flow, no session, and no signing step for requests you send us, only for webhooks we send you. Keys are created, scoped and revoked from the dashboard; there is no endpoint that mints one, because an API that can create its own credentials cannot be contained by revoking one.

Either header works
curl https://podcaststotext.com/api/v1/usage \
  -H "Authorization: Bearer ptt_a3f9c1e2b7d4…"

# or, if a bearer header is awkward in your client
curl https://podcaststotext.com/api/v1/usage \
  -H "X-API-Key: ptt_a3f9c1e2b7d4…"

Keys look like ptt_ followed by 40 hex characters. Only a SHA-256 hash is stored, so the raw key is shown once at creation and is unrecoverable afterwards. That call is also the cheapest way to check a key: a 200 returns the plan, the credit balance and scopes, so you can confirm what the key actually carries.

Unknown, revoked and expired all return the same 401. Deliberately: distinguishing them would confirm which keys exist. If a key stops working, check the dashboard rather than inferring from the response.

Scopes

A key carries the scopes you chose when creating it. An endpoint refuses with 403 insufficient_scope if any required scope is missing: a read-only key cannot spend a credit or publish a page, whichever door it comes through. Give an agent the narrowest key that does its job.

ScopeGrantsPlan
transcriptions:readList, fetch, export, captions, usage, podcast lookupFree
transcriptions:writeCreate and delete transcriptions, presigned uploadsPaid
shows:readList connected shows and webhook endpointsFree
shows:writePublish episodes, manage webhook endpointsPaid
ai:writeShow notes, chapters and translation, over both REST and MCPPaid

A 403 plan_required is the other refusal, and it has two causes: the plan has no API access at all, or it has read access but not write. The scope check runs first, so a key failing both is told about the scope, that is the one you can fix without spending money.

403 insufficient_scope
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the \"transcriptions:write\" scope.",
    "how_to_fix": "Create a new key with the \"transcriptions:write\" scope at https://podcaststotext.com/dashboard/api-keys."
  }
}

Handling keys

  • Name a key after where it runs: "staging worker", "Claude Desktop". When you revoke one in a hurry you will be choosing from a list of names.
  • Copy it at creation. It is displayed once, and only the hash is stored.
  • Environment variable or a secret manager; never in source control, and never in a client-side bundle, since anything named NEXT_PUBLIC_*, VITE_* or REACT_APP_* is public.
  • One key per environment and per workload, so rotating or exhausting one does not touch the others.
  • A key can carry an expiry date, after which it 401s like any other unusable key, which is useful for a contractor or a demo.

To rotate: create the replacement with the same scopes, deploy it and confirm traffic on it (last_used_at updates on every authenticated request), then revoke the old one. Both are valid at once, so there is no gap.

If a key leaks, revoke it, do not just stop using it. Deleting the line that referenced it changes nothing; the key authenticates until revoked. Revoke first, investigate second. Rewriting git history does not un-publish a committed key.