curl --request POST \
--url https://k8mfogcvz4.execute-api.us-east-1.amazonaws.com/prod/v1/quotes \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"chart": {
"category": "News",
"size": 20,
"language": "en"
},
"episodes_per_show": 2
}
'{
"quote_id": "qte_9k2fA7bQ3xzM1LpN",
"source": "shows",
"episodes_per_show": 2,
"entries": [
{
"episode_id": "ep_rwg4do2imnjyhaj7",
"show_id": "sh_k4n7qzvw2mtxyabc",
"feed_url": "https://feeds.example.com/vergecast.xml",
"guid": "vergecast-2026-09-05",
"show_title": "The Vergecast",
"episode_title": "The week in gadgets",
"published_at": "2026-09-05T09:00:00Z",
"is_cached": true,
"estimated_credits": 1,
"quote_ceiling_credits": 1,
"quote_basis": "feed_metadata",
"declared_duration_seconds": 3600
},
{
"episode_id": "ep_3iicyxg6ymbv75gy",
"show_id": "sh_k4n7qzvw2mtxyabc",
"feed_url": "https://feeds.example.com/vergecast.xml",
"guid": "vergecast-2026-09-02",
"show_title": "The Vergecast",
"episode_title": "Foldables, again",
"published_at": "2026-09-02T09:00:00Z",
"is_cached": false,
"estimated_credits": 60,
"quote_ceiling_credits": 75,
"quote_basis": "feed_metadata",
"declared_duration_seconds": 3600
},
{
"episode_id": "ep_uy4pqvjsmeyrhavz",
"show_id": "sh_x5z7grcs3p4y5h62",
"feed_url": "https://feeds.example.com/decoder.xml",
"guid": "decoder-2026-09-04",
"show_title": "https://feeds.example.com/decoder.xml",
"episode_title": null,
"published_at": "2026-09-04T09:00:00Z",
"is_cached": false,
"estimated_credits": 45,
"quote_ceiling_credits": 57,
"quote_basis": "feed_metadata",
"declared_duration_seconds": 2700
}
],
"excluded": [
{
"feed_url": "https://feeds.example.com/decoder.xml",
"guid": "decoder-2026-08-28",
"title": "https://feeds.example.com/decoder.xml — Decoder, 28 August",
"reason": "no_declared_duration",
"detail": "the feed declares no <itunes:duration>, so there is nothing to price from"
}
],
"clamps": [],
"cached_members": 1,
"uncached_members": 2,
"total_ceiling_credits": 133,
"remaining_open_jobs": 2,
"balance_credits": 2840,
"reserved_credits": 0,
"expires_at": "2026-09-08T09:15:00Z",
"created_at": "2026-09-08T09:00:00Z"
}{
"error": {
"code": "invalid_request",
"type": "invalid_request",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"retryable": true
}
}{
"error": {
"code": "invalid_request",
"type": "invalid_request",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"retryable": true
}
}{
"error": {
"code": "invalid_request",
"type": "invalid_request",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"retryable": true
}
}{
"error": {
"code": "invalid_request",
"type": "invalid_request",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"retryable": true
}
}{
"error": {
"code": "invalid_request",
"type": "invalid_request",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"retryable": true
}
}{
"error": {
"code": "invalid_request",
"type": "invalid_request",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"retryable": true
}
}Price a selection without spending
Turns a chart or a list of named shows into a priced selection and records it. Reserves nothing: the quote holds no credit and creates no job; a separate confirm operation, keyed on quote_id, is what spends.
The selection rule, stated once. Each show contributes either the episodes named in its episode_ids, or — when none are named — its newest episodes_per_show (default 1). Two tier bounds apply: the chart size, and the total number of episodes one selection may come to. No show may give more than that total on its own, which is the only thing that bounds depth: how far back a selection reaches is the caller’s to state, not the plan’s to grant. Every bound that bit is reported in clamps; every show and every episode that did not make it into entries is reported in excluded with a reason a caller can read. Nothing shrinks silently.
Two request shapes. chart asks the provider for a category chart (clamped exactly as GET /v1/charts clamps it) and, because nobody chose those shows, excludes music-led ones unless include_music_led is true. shows names feed URLs — usually straight from a search result — and takes every one as chosen; no category data is consulted for a named show.
One feed fetch per show. The feeds read to select episodes are the ones the quote prices from; nothing is downloaded twice. Each phase runs under its own wall-clock budget inside the request, and an episode the budget did not reach comes back in excluded as budget_exceeded rather than failing the request.
Every priced entry carries the domain’s estimate and its ceiling; a cached entry is priced at what a cached read costs this account — the floor for the account whose job produced the transcript, a fraction of its measured audio minutes for anyone else — with no ceiling above it. remaining_open_jobs is the caller’s fan-out headroom under their tier’s open-job cap — jobs reserved and not yet terminal — reported so a selection can be sized before spending. It is enforced at confirm, not here. balance_credits and reserved_credits ride beside it off the same account row, so an agent sees what it holds at the moment it decides to spend; they are reported on the same terms, and a balance too small for total_ceiling_credits does not refuse the quote.
curl --request POST \
--url https://k8mfogcvz4.execute-api.us-east-1.amazonaws.com/prod/v1/quotes \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"chart": {
"category": "News",
"size": 20,
"language": "en"
},
"episodes_per_show": 2
}
'{
"quote_id": "qte_9k2fA7bQ3xzM1LpN",
"source": "shows",
"episodes_per_show": 2,
"entries": [
{
"episode_id": "ep_rwg4do2imnjyhaj7",
"show_id": "sh_k4n7qzvw2mtxyabc",
"feed_url": "https://feeds.example.com/vergecast.xml",
"guid": "vergecast-2026-09-05",
"show_title": "The Vergecast",
"episode_title": "The week in gadgets",
"published_at": "2026-09-05T09:00:00Z",
"is_cached": true,
"estimated_credits": 1,
"quote_ceiling_credits": 1,
"quote_basis": "feed_metadata",
"declared_duration_seconds": 3600
},
{
"episode_id": "ep_3iicyxg6ymbv75gy",
"show_id": "sh_k4n7qzvw2mtxyabc",
"feed_url": "https://feeds.example.com/vergecast.xml",
"guid": "vergecast-2026-09-02",
"show_title": "The Vergecast",
"episode_title": "Foldables, again",
"published_at": "2026-09-02T09:00:00Z",
"is_cached": false,
"estimated_credits": 60,
"quote_ceiling_credits": 75,
"quote_basis": "feed_metadata",
"declared_duration_seconds": 3600
},
{
"episode_id": "ep_uy4pqvjsmeyrhavz",
"show_id": "sh_x5z7grcs3p4y5h62",
"feed_url": "https://feeds.example.com/decoder.xml",
"guid": "decoder-2026-09-04",
"show_title": "https://feeds.example.com/decoder.xml",
"episode_title": null,
"published_at": "2026-09-04T09:00:00Z",
"is_cached": false,
"estimated_credits": 45,
"quote_ceiling_credits": 57,
"quote_basis": "feed_metadata",
"declared_duration_seconds": 2700
}
],
"excluded": [
{
"feed_url": "https://feeds.example.com/decoder.xml",
"guid": "decoder-2026-08-28",
"title": "https://feeds.example.com/decoder.xml — Decoder, 28 August",
"reason": "no_declared_duration",
"detail": "the feed declares no <itunes:duration>, so there is nothing to price from"
}
],
"clamps": [],
"cached_members": 1,
"uncached_members": 2,
"total_ceiling_credits": 133,
"remaining_open_jobs": 2,
"balance_credits": 2840,
"reserved_credits": 0,
"expires_at": "2026-09-08T09:15:00Z",
"created_at": "2026-09-08T09:00:00Z"
}{
"error": {
"code": "invalid_request",
"type": "invalid_request",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"retryable": true
}
}{
"error": {
"code": "invalid_request",
"type": "invalid_request",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"retryable": true
}
}{
"error": {
"code": "invalid_request",
"type": "invalid_request",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"retryable": true
}
}{
"error": {
"code": "invalid_request",
"type": "invalid_request",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"retryable": true
}
}{
"error": {
"code": "invalid_request",
"type": "invalid_request",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"retryable": true
}
}{
"error": {
"code": "invalid_request",
"type": "invalid_request",
"message": "<string>",
"doc_url": "<string>",
"request_id": "<string>",
"retryable": true
}
}Authorizations
Authorization: Bearer hk_live_... for live keys or Authorization: Bearer hk_test_... for test-mode keys. hk_test_ keys resolve real public catalog metadata but return deterministic committed fixtures, never call inference, and never mutate live credits. This is the only transport for the credential: the x-api-key alias once documented was removed in 0.2.0, because the edge authorizer reads Authorization as its single identity source and a request on any other header is refused before it is authenticated.
Never accepted on a dashboardJwt operation, and there are no exceptions. GET /v1/usage and GET /v1/limits briefly declared both schemes (0.8.0); that was withdrawn in 0.8.1 because no deployed route could honor it — both operations are served by the control-plane API, whose authorizer verifies a Cognito token and refuses an hk_live_ credential on shape, and the customer API does not route either path. An API-key holder reads its balance and reservation from QuoteResponse, which carries balance_credits and reserved_credits on every quote. Every operation in this document takes one scheme or the other and refuses the wrong one as unauthenticated.
Body
- Option 1
- Option 2
1 - 100 elementsShow child attributes
Show child attributes
The N in "the newest N episodes per show". Ignored for a show whose episode_ids name its episodes. Clamped to the plan's whole selection total when it exceeds it, and reported.
1 <= x <= 100The music-led switch. Named shows are taken as chosen, so it changes nothing here; it is accepted so one client can send one body shape for both sources.
Response
The recorded quote — priced entries, exclusions, clamps, totals, and headroom.
The recorded quote. total_ceiling_credits is the sum of the entries' ceilings and cached_members the count of entries with is_cached: true — both derived by the store at write time, never taken from a caller. The quote is valid until expires_at; a confirm after that is refused.
balance_credits and reserved_credits are the caller's account as it stood when the quote was priced, read off the same row remaining_open_jobs was counted from — so an agent can see what it holds at the moment it decides to spend without a second call. Like the headroom beside them they are reported, never enforced: a quote reserves nothing, so a balance below total_ceiling_credits is a fact to act on rather than a refusal, and affordability is decided by the confirm.
Opaque, server-generated quote identifier.
40^qte_[A-Za-z0-9]{16,32}$Which request shape produced this quote.
shows, chart The N after the tier had its say.
x >= 11 - 100 elementsShow child attributes
Show child attributes
Show child attributes
Show child attributes
3Show child attributes
Show child attributes
x >= 0x >= 0A non-negative JavaScript-safe integer credit amount. The upper bound matches Number.MAX_SAFE_INTEGER, which is enforced by the domain ledger so arithmetic and JSON round-trips cannot silently lose cents worth of credit precision.
0 <= x <= 9007199254740991How many more jobs the caller's tier lets them have open — reserved and not yet terminal — right now, so a selection can be sized before it is confirmed. Reported here, enforced at confirm.
x >= 0A non-negative JavaScript-safe integer credit amount. The upper bound matches Number.MAX_SAFE_INTEGER, which is enforced by the domain ledger so arithmetic and JSON round-trips cannot silently lose cents worth of credit precision.
0 <= x <= 9007199254740991A non-negative JavaScript-safe integer credit amount. The upper bound matches Number.MAX_SAFE_INTEGER, which is enforced by the domain ledger so arithmetic and JSON round-trips cannot silently lose cents worth of credit precision.
0 <= x <= 9007199254740991
