Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.4.1

Paid reads use credits from your plan, then your on-demand budget.

## 0.4.0

Generated from the v1 document of 2026-10-02. The document dropped from 88 operations to 36. Routes that left it still serve over HTTP, but the SDK no longer has methods for them.
Expand All @@ -15,9 +19,9 @@ Added.

Breaking changes from 0.3.

- Premium is one read. `transcripts.get(video_id, quality="premium")` answers `ready` (200) when the account owns the transcript. Otherwise it buys the whole video within the plan and the account's on-demand budget and answers `pending` (202) with the `job` and a `Retry-After` header. Read again after `Retry-After`. Repeated reads join the same purchase and never buy twice. When the last purchase for the video failed or was refunded, the read answers `failed` (200) with the `job` and `last_attempt` and buys nothing; pass `retry=True` to buy it again. `TranscriptResult` is now `TranscriptResult_Ready | TranscriptResult_Pending | TranscriptResult_Failed`. Priced refusals carry one `RefusedQuote` type. The `preparation_required` state and `TranscriptResult_PreparationRequired` are gone.
- Premium is one read. `transcripts.get(video_id, quality="premium")` answers `ready` (200) when the account owns the transcript. Otherwise it starts transcribing the whole video, using credits from the plan and then the account's on-demand budget, and answers `pending` (202) with the `job` and a `Retry-After` header. Read again after `Retry-After`. Repeated reads join the same job and never use credits twice. When the last transcription of the video failed or was refunded, the read answers `failed` (200) with the `job` and `last_attempt` and uses no credits; pass `retry=True` to transcribe it again, which uses credits again. `TranscriptResult` is now `TranscriptResult_Ready | TranscriptResult_Pending | TranscriptResult_Failed`. Priced refusals carry one `RefusedQuote` type. The `preparation_required` state and `TranscriptResult_PreparationRequired` are gone.
- `transcripts.prepare_and_wait` is removed, along with `PreparationError`, `PreparationFailedError`, `PreparationTimeoutError` and `PremiumUnavailableError`. Loop on `transcripts.get(..., quality="premium")` until `state == "ready"`. The README has the loop.
- `transcripts.request` and `transcripts.status` are removed, with the `TranscriptRequestSubmitResponse` type. The Premium read buys and reports its own job. `transcripts.list_requests` still lists past purchases.
- `transcripts.request` and `transcripts.status` are removed, with the `TranscriptRequestSubmitResponse` type. The Premium read starts and reports its own job. `transcripts.list_requests` still lists past Premium transcriptions.
- A Premium refusal raises from the read itself. `PaymentRequiredError` (402) carries `quota_exceeded` or `spend_limit_exceeded`, and `ForbiddenError` (403) carries `paid_plan_required`. Nothing is charged.
- Error extras moved under `error.details`. `body.quote` is now `body.error.details.quote`, `body.existing_request_id` is `body.error.details.existing_request_id`, and `body.existing_id` (409 `tracker_already_exists`) is `body.error.details.existing_id`.
- Reads take ids. `mentions.list` and `recommendations.list` require `entity_id` (`ent_N`), and `channel_id` takes a YouTube channel id (`UC` plus 22 characters). `entity_name`, `entity_type` and `channel_name` are gone. A name where an id belongs raises `BadRequestError` with code `id_required` and names the parameter. Resolve names first with `entities.resolve`.
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ Every dated read takes `after` and `before`. The window is half-open, `[after, b

## Premium transcripts

A Premium read is one call. It answers 200 `ready` when the account owns the transcript. Otherwise it buys the whole video within the account's plan and answers 202 `pending` with the job. Included credits are spent first, then the account's on-demand budget. The budget is the approval, so the call takes no price ceiling.
A Premium read is one call. It answers 200 `ready` when the account owns the transcript. Otherwise it starts transcribing the whole video and answers 202 `pending` with the job. The read uses credits from your plan, then your on-demand budget. You set that budget in the dashboard, and it is the approval, so the call takes no price ceiling.

Read again after `Retry-After`. Repeated reads join the same purchase and never buy twice.
Read again after `Retry-After`. Repeated reads join the same job and never use credits twice.

```python
import time
Expand All @@ -60,7 +60,7 @@ else:

The timeout only stops this polling loop. It does not cancel the job. Resume with the same video id and `quality="premium"`; do not set `retry=True` while the job is pending.

`read.data` is a `TranscriptResult`, discriminated on `state`. `ready` carries the transcript. `pending` carries `job`, with `eta_seconds`, `next_poll_seconds` and `charge`. `failed` means the last purchase failed or was refunded; it carries `job` and `last_attempt`, buys nothing, and `retry=True` buys it again. Without `with_raw_response`, `client.transcripts.get(...)` returns the same union without the status and headers.
`read.data` is a `TranscriptResult`, discriminated on `state`. `ready` carries the transcript. `pending` carries `job`, with `eta_seconds`, `next_poll_seconds` and `charge`. `failed` means the last transcription failed or was refunded; it carries `job` and `last_attempt` and uses no credits. `retry=True` transcribes it again and uses credits again. Without `with_raw_response`, `client.transcripts.get(...)` returns the same union without the status and headers.

A quote is free and changes nothing.

Expand All @@ -69,9 +69,9 @@ quote = client.transcripts.quote("dQw4w9WgXcQ")
print(quote.quote.rows, quote.charge.amount, quote.charge.from_, quote.max_on_demand_cents)
```

`client.transcripts.list_requests()` lists past purchases with their state.
`client.transcripts.list_requests()` lists past Premium transcriptions with their state.

A read without `quality="premium"` returns captions and buys nothing.
A read without `quality="premium"` returns captions and starts no transcription.

## Errors

Expand Down Expand Up @@ -166,7 +166,7 @@ uv run python -m unittest discover -s tests -v
uv build
```

The tests use a local HTTP server that returns the bodies the API sends. They check both client variants, the ready and pending reads, typed refusals with their quote, the query and body each call sends, and opaque pagination. No live API key or purchase is required.
The tests use a local HTTP server that returns the bodies the API sends. They check both client variants, the ready and pending reads, typed refusals with their quote, the query and body each call sends, and opaque pagination. They need no live API key and use no credits.

## License

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.4.0
0.4.1
Loading
Loading