diff --git a/CHANGELOG.md b/CHANGELOG.md index 72fdcf5..0af12c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.6.3 + +Generated from the API document of 2026-11-09. + +- Transcript responses expose `base_language` and `translation_languages`. +- Search passages expose `match` (`phrase`, `all_words`, or `partial`) and nullable `line_start_seconds`. Watch links target the matching line when available. +- Refresh reference descriptions for search filters, transcript windows, alert delivery, and temporary service errors. +- Update package descriptions and README introductions for transcript search and monitoring. + ## 0.6.2 - Lead the README with tested examples for transcript reads, quotes with source links, and company monitoring. diff --git a/README.md b/README.md index 9ec9f18..ca178f0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Arcmira for Python -Pull YouTube transcripts, search spoken passages, and monitor company mentions with Arcmira. Results include timestamps and source links for research, citations, and agent workflows. +YouTube transcript search and monitoring for your AI: exact quotes, full transcripts and speakers. [Arcmira](https://arcmira.com) · [Get an API key](https://arcmira.com/docs/authentication) · [API docs](https://arcmira.com/docs) · [Pricing](https://arcmira.com/pricing) @@ -154,6 +154,8 @@ Entity filters take ids. Resolve names first; explain a `suggested` match and as ## Use Arcmira with an AI agent +Add "use arcmira" to any question to send it to Arcmira first. + For tasks such as "pull YouTube transcripts", "find quotes with timestamps", "research podcast sponsors", or "monitor company mentions", connect the [Arcmira MCP server](https://github.com/arcmira/mcp). The [agent index](https://arcmira.com/llms.txt) links to current API and citation guidance. ## Reference diff --git a/VERSION b/VERSION index b616048..844f6a9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.2 +0.6.3 diff --git a/fern/openapi.json b/fern/openapi.json index 1deee3f..ddcb8cb 100644 --- a/fern/openapi.json +++ b/fern/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.1.0", "info": { "title": "Arcmira API", - "description": "Search YouTube transcripts for timestamped passages. Find mentions of people, organizations, products and topics; research channel sponsors and recommendations; retrieve creator captions or Premium transcripts with speaker identification; and monitor entities for new mentions. Official API guides: https://arcmira.com/docs. An explicit Premium read uses credits from the account's plan, then its on-demand budget.", + "description": "YouTube transcript search and monitoring. Search transcripts for timestamped passages. Find mentions of people, organizations, products and topics; research channel sponsors and recommendations; retrieve creator captions or Premium transcripts with speaker identification; and monitor entities for new mentions. Official API guides: https://arcmira.com/docs. An explicit Premium read uses credits from the account's plan, then its on-demand budget.", "version": "1.0.0", "contact": { "name": "Arcmira", @@ -394,7 +394,7 @@ { "code": "fair_use_cap", "type": "quota_exceeded", - "description": "Ultra plan credits cover 80 Premium transcript hours a month, and this month's are used. Past them a Premium transcript uses granted or on-demand credits, and the account has none. Nothing was charged. details.limit carries the hours and what is used. An admin turns on on-demand usage or raises its budget, or the hours reset with the month." + "description": "A fair-use cap on the plan's credits is reached for this use this month, and the account has no granted or on-demand credits to use instead. Nothing was charged. details.limit carries the cap and what is used. An admin turns on on-demand usage or raises its budget, or the cap resets with the month." }, { "code": "feature_not_available", @@ -599,6 +599,11 @@ "type": "server_error", "description": "Unexpected failure. Retry with backoff and quote request_id if it persists." }, + { + "code": "service_unavailable", + "type": "server_error", + "description": "The database was briefly overloaded or restarting (HTTP 503). Retry-After carries the wait; send the same request again." + }, { "code": "signup_code_invalid", "type": "invalid_request_error", @@ -647,7 +652,7 @@ { "code": "transcript_fetching", "type": "server_error", - "description": "The caption track is being fetched now (HTTP 503). Retry-After carries the wait; nothing was charged." + "description": "The caption track is being fetched, or the transcript translated, now (HTTP 503). Retry-After carries the wait; nothing was charged." }, { "code": "transcript_requires_account", @@ -2436,7 +2441,7 @@ "string", "null" ], - "description": "One-sentence description of the mention context. Null when not generated." + "description": "One sentence on why the entity comes up in this video, written to sit under the quoted line. Some mentions read in October 2026 hold the quoted transcript line instead. Null when not generated." }, "confidence": { "type": [ @@ -3889,13 +3894,15 @@ "John Coogan" ], "source": "creator_captions", - "source_label": "Creator captions", + "source_label": "3rd Party (Video Creator)", "published_at": "2026-08-04T17:00:00.000Z", - "text": "Ramp has been on the show for a while now and the pitch is still the same, spend less time on expenses.", + "text": "Ramp corporate cards have been on the show for a while now, and the pitch is still the same: spend less time on expenses.", "start_seconds": 4787, + "line_start_seconds": 4787, "watch_url": "/watch?v=dQw4w9WgXcQ&t=4787", "cite_line": "[TBPN | Tuesday, August 4](/watch?v=dQw4w9WgXcQ&t=4787) · @1:19:47 · TBPN · Aug 4, 2026", "score": 0.71, + "match": "phrase", "about": [ { "id": "ent_14", @@ -4024,23 +4031,39 @@ "integer", "null" ], - "description": "Offset of the slice in the video, in seconds." + "description": "Where the slice starts in the video, in seconds. The line that matched q can be up to a minute later; line_start_seconds says when it is said." + }, + "line_start_seconds": { + "type": [ + "integer", + "null" + ], + "description": "When the line that best matches q starts, in whole seconds from the start of the video: the line where q starts as a phrase, otherwise the first of the fewest consecutive lines holding the most words of q. With by, the person's own line. watch_url and cite_line open here. Null when no line can be placed, for example on a passage indexed before line times were stored; watch_url and cite_line then open at start_seconds." }, "watch_url": { "type": "string", - "description": "Absolute watch URL on arcmira.com with the timestamp, e.g. https://arcmira.com/watch?v=...&t=4787." + "description": "Absolute watch URL on arcmira.com at line_start_seconds (start_seconds when it is null), e.g. https://arcmira.com/watch?v=...&t=4787." }, "cite_line": { "type": [ "string", "null" ], - "description": "A ready citation line: title, clock, channel, date." + "description": "A ready citation line: title, clock, channel, date. The clock is the second watch_url opens at." }, "score": { "type": "number", "description": "Retrieval score. Higher is a closer match. Not comparable across calls." }, + "match": { + "type": "string", + "enum": [ + "phrase", + "all_words", + "partial" + ], + "description": "How the passage holds q, ignoring case and punctuation: phrase holds every word of q in order, all_words holds each content word somewhere, partial holds only some. Passages holding the phrase or every word rank first. When no chunk is a phrase match, note says so." + }, "about": { "type": "array", "items": { @@ -4064,8 +4087,10 @@ "published_at", "text", "start_seconds", + "line_start_seconds", "watch_url", - "score" + "score", + "match" ] }, "EntityMomentumResponse": { @@ -5667,7 +5692,7 @@ }, "status": { "type": "string", - "description": "Delivery status. Values: pending (queued for delivery), sent (delivered), failed (delivery failed; see error_message), skipped (delivery intentionally skipped)." + "description": "Delivery status. Values: pending (queued for delivery), sending (handed to the destination, answer not yet recorded), sent (delivered), unconfirmed (handed to the destination with no answer recorded; counted as delivered), failed (delivery failed; see error_message), skipped (delivery intentionally skipped)." }, "final_status": { "type": [ @@ -6122,7 +6147,11 @@ }, "language": { "type": "string", - "description": "The resolved track code, asr-en style when the track is automatic." + "description": "The language of the lines: the resolved track code (asr-en style when the track is automatic) or, when the transcript was translated, the code it was translated into." + }, + "base_language": { + "type": "string", + "description": "The transcript's own language, as a code (en, es): the original caption track's, or the language a Premium transcript was spoken and transcribed in. language= returns the transcript in it; a language other than base_language means the lines were translated." }, "languages": { "type": "array", @@ -6131,6 +6160,13 @@ }, "description": "Every caption track the video offers. Empty when we did not list them on this call." }, + "translation_languages": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The codes language can name on this read. On captions, only the languages YouTube makes available for the video (its own tracks, and its auto-translate languages); on Premium, every language Arcmira translates into, at the Premium rate." + }, "lines": { "type": "array", "items": { @@ -6247,7 +6283,7 @@ }, "rows_billed": { "type": "integer", - "description": "Credits this captions read used, in rows (a row is 4 credits): 4 credits per started 15 minutes. 0 on a repeat of the same video, quality, language, and range inside the 7 day dedupe window. Premium reads report 0, because the Premium transcript job carries their charge." + "description": "Credits this captions read used, in rows (a row is 4 credits): 4 credits per started 15 minutes. 0 on a repeat of the same video, quality, language, translation, and range inside the 7 day dedupe window. Premium reads report 0, because the Premium transcript job carries their charge, except the read that pays for a Premium transcript in another language, which reports its rows." }, "as_of": { "type": [ @@ -6413,7 +6449,9 @@ "quality", "source", "language", + "base_language", "languages", + "translation_languages", "rows_billed", "as_of", "note" @@ -6432,6 +6470,7 @@ "quality": "captions", "source": "creator_captions", "language": "en", + "base_language": "en", "languages": [ { "code": "en", @@ -6439,6 +6478,17 @@ "generated": false } ], + "translation_languages": [ + "ar", + "de", + "es", + "fr", + "hi", + "ja", + "ko", + "pt", + "zh-Hans" + ], "lines": [ { "start": 4787, @@ -6468,6 +6518,7 @@ "quality": "premium", "source": "arcmira_premium", "language": "en", + "base_language": "en", "languages": [ { "code": "en", @@ -6475,6 +6526,17 @@ "generated": false } ], + "translation_languages": [ + "ar", + "de", + "es", + "fr", + "hi", + "ja", + "ko", + "pt", + "zh-Hans" + ], "lines": [ { "start": 612, @@ -7518,7 +7580,7 @@ ], "operationId": "list_mentions", "summary": "Search mentions across media", - "description": "Cursor-paginated mentions filtered by entity (entity_id is required; resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter), channel (channel_id), text query, sentiment, appearance flag, and publication window [after, before). The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing mentions remain live. Positions are start_seconds and end_seconds (integer seconds; 0 means full episode). is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan.", + "description": "Find where a person, company, product, or topic is mentioned in indexed videos and podcasts. For a person, set `is_appearance` to true to find media appearances, false for mentions by others, or omit it for both. Results include a description, source video, and available timestamps. Cursor-paginated, filtered by entity (`entity_id` is required; resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter), channel (`channel_id`), text query, sentiment, appearance flag, and publication window [after, before). The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing mentions remain live. Positions are start_seconds and end_seconds (integer seconds; 0 means full episode). is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan.", "security": [ { "bearerAuth": [] @@ -7879,7 +7941,7 @@ ], "operationId": "submit_feedback", "summary": "Submit feedback and corrections for a prior API query", - "description": "Attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status \"logged\"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alerts: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert id, and every referenced alert must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with nothing to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. experience feedback says how a task went as a whole rather than correcting a result: it requires category and notes, refuses corrections (400 invalid_feedback_request), and needs no query. category and mcp_call_id, when sent, are recorded in the stored query.", + "description": "Help shape Arcmira. Report a wrong result, request sources or capabilities, or tell us what would make your workflow more useful. To correct a result, attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status \"logged\"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alerts: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert id, and every referenced alert must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with nothing to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. experience feedback says how a task went as a whole rather than correcting a result: it requires category and notes, refuses corrections (400 invalid_feedback_request), and needs no query. category and mcp_call_id, when sent, are recorded in the stored query.", "security": [ { "bearerAuth": [] @@ -8390,7 +8452,7 @@ ], "operationId": "search", "summary": "Retrieve spoken transcript slices for one topic", - "description": "Search indexed YouTube and podcast transcripts for short spoken slices. Each result includes spoken text, a watch URL, and a publish date. Scope with channel_ids (or channel) and entity_ids (a person id filters to that person's appearances); narrow to passages about entities with about, to a speaker with by, and to sponsored, organic or mention passages with kind. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. An after later than the plan's freshness gate is refused with freshness_requires_paid rather than widened. A window the gate cut carries freshness (delay_days, served_through), unlock, and a note that says where results on this plan stop. Uses 4 credits per chunk returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.", + "description": "Search indexed YouTube and podcast transcripts for passages matching a word or phrase. Returns passage text, source metadata, publication dates, and timestamped watch links. Narrow to passages about an entity with `about`, or passages where a person speaks with `by`. Scope with `channel_ids` (or `channel`) and `entity_ids` (a person id filters to that person's appearances). Filter to sponsored, organic or mention passages with `kind`. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. An after later than the plan's freshness gate is refused with freshness_requires_paid rather than widened. A window the gate cut carries freshness (delay_days, served_through), unlock, and a note that says where results on this plan stop. Uses 4 credits per chunk returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.", "security": [ { "bearerAuth": [] @@ -8451,20 +8513,20 @@ { "schema": { "type": "string", - "description": "Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people says the query words (each line of a chunk is labeled with its speaker); a non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve." + "description": "Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people speaks the query words. A passage can include other speakers' lines; each line with a known speaker starts with that speaker's name. A non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve." }, "required": false, - "description": "Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people says the query words (each line of a chunk is labeled with its speaker); a non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve.", + "description": "Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people speaks the query words. A passage can include other speakers' lines; each line with a known speaker starts with that speaker's name. A non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve.", "name": "by", "in": "query" }, { "schema": { "type": "string", - "description": "Comma-separated passage classes: sponsored, organic, mention. Combine with about to read what was said about a brand in ad reads or in organic talk." + "description": "Comma-separated passage classes: sponsored, organic, mention. Combine with about to read what was said about a brand in ad reads or in organic talk. organic and mention leave out every passage tagged as an ad read, including one that also names other brands; add sponsored to include them." }, "required": false, - "description": "Comma-separated passage classes: sponsored, organic, mention. Combine with about to read what was said about a brand in ad reads or in organic talk.", + "description": "Comma-separated passage classes: sponsored, organic, mention. Combine with about to read what was said about a brand in ad reads or in organic talk. organic and mention leave out every passage tagged as an ad read, including one that also names other brands; add sponsored to include them.", "name": "kind", "in": "query" }, @@ -8610,7 +8672,7 @@ ], "operationId": "get_entity_momentum", "summary": "Spoken-web heat for one entity", - "description": "Mentions in a lookback window of days (7 to 365, default 30) against the same number of days before it, an absolute-delta verdict on that window (accelerating, flat, fading, none), the newest media date, and the top shows in the window. Pass the window the rest of your answer uses: days=90 for a 90-day answer. The fixed 7, 30 and 90 day counts stay on every card. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic for the window; otherwise that field is absent and access names the gate. On a plan with a freshness gate, the window ends at the cutoff and the card carries freshness, unlock, and a note that says where results stop. Uses 10 credits from your plan at any window, the default included. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.", + "description": "Compare an entity's mention volume with the preceding period. Returns the counts, absolute and percentage change, momentum verdict, latest source date, and top shows. Choose a rolling window of 7 to 365 days with `days` (default 30). The preceding period is the same number of days before the window, and the verdict (accelerating, flat, fading, none) reads the absolute change. Pass the window the rest of your answer uses: days=90 for a 90-day answer. The fixed 7, 30 and 90 day counts stay on every card. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic for the window; otherwise that field is absent and access names the gate. On a plan with a freshness gate, the window ends at the cutoff and the card carries freshness, unlock, and a note that says where results stop. Uses 10 credits from your plan at any window, the default included. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.", "security": [ { "bearerAuth": [] @@ -8713,7 +8775,7 @@ ], "operationId": "get_channel_coverage", "summary": "What the index holds for a channel", - "description": "How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; channel backfill is not available yet. Free; uses no credits. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.", + "description": "How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; to ask for a channel, send feedback with its channel id. Free; uses no credits. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.", "security": [ { "bearerAuth": [] @@ -8800,7 +8862,7 @@ ], "operationId": "list_channel_videos", "summary": "Newest indexed videos of a channel", - "description": "The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing videos remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Uses 4 credits per video returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.", + "description": "The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing videos remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; this route cannot add a channel. Uses 4 credits per video returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.", "security": [ { "bearerAuth": [] @@ -10695,7 +10757,7 @@ ], "operationId": "get_transcript", "summary": "Get a video transcript", - "description": "Caption reads use 4 credits per started 15 minutes. quality=premium is one read. An owned transcript answers 200 ready at no charge. Otherwise this call starts a Premium transcript of the whole video, 300 credits per started 15 minutes, using credits from the account's plan first and then the account's on-demand budget up to its limit, and answers 202 pending with the job and Retry-After until the transcript is ready. Read again after Retry-After; repeated reads join the same job and never charge twice. When the last Premium transcript for the video failed, the read answers 200 state failed with the job and last_attempt and charges nothing; retry=true starts a new one. When the plan or the budget blocks, 403 paid_plan_required (with unlock) or 402 quota_exceeded or spend_limit_exceeded carries the price in quote and the limit it met in limit, and nothing is charged. A default-premium account with nothing owned reads captions with a note. On captions, start and end set the billed window: 4 credits per started 15 minutes of end minus start. On Premium they only trim the returned content, and the charge is the whole video. language selects caption tracks, timestamps=false returns paragraphs. Premium lines carry speaker and index, and the body carries speakers and revision.", + "description": "Caption reads use 4 credits per started 15 minutes. quality=premium is one read. An owned transcript answers 200 ready at no charge. Otherwise this call starts a Premium transcript of the whole video, 300 credits per started 15 minutes, using credits from the account's plan first and then the account's on-demand budget up to its limit, and answers 202 pending with the job and Retry-After until the transcript is ready. Read again after Retry-After; repeated reads join the same job and never charge twice. When the last Premium transcript for the video failed, the read answers 200 state failed with the job and last_attempt and charges nothing; retry=true starts a new one. When the plan or the budget blocks, 403 paid_plan_required (with unlock) or 402 quota_exceeded or spend_limit_exceeded carries the price in quote and the limit it met in limit, and nothing is charged. A default-premium account with nothing owned reads captions with a note. On captions, start and end set the billed window: 4 credits per started 15 minutes of end minus start. On Premium they only trim the returned content, and the charge is the whole video. language selects caption tracks, timestamps=false returns paragraphs. Premium lines carry speaker and index, and the body carries speakers and revision. language picks the language: language= returns the base transcript (base_language), and a language the transcript is not in returns it translated, line for line with the same timestamps and speakers. Captions offer only the languages YouTube makes available for the video (translation_languages), billed as a caption read; any other language is Premium: an owned Premium transcript in another language costs the Premium rate once per account and language.", "security": [ { "bearerAuth": [] @@ -10729,10 +10791,10 @@ { "schema": { "type": "string", - "description": "Comma-separated caption language priority list, at most 5, tried in order (e.g. \"de,en\"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers." + "description": "The language to read the transcript in, as a code (e.g. \"es\") or a comma-separated priority list of at most 5 (e.g. \"de,en\"); asr picks the first automatic caption track and asr- a specific one. Every transcript has a base language, base_language in the response: the language of the original caption track, or the language a Premium transcript was spoken and transcribed in. language= with no value returns the base transcript; so does a code in the base language. Omitted, the account default applies (en unless changed), falling back to the base transcript. When no code in the list matches, the transcript is translated into the first code, line for line with the same timestamps and speakers. Captions translate only into the languages YouTube makes available for the video (translation_languages in the response, or GET /v1/videos/{video_id}/captions), billed as a caption read of the window, 4 credits per started 15 minutes; any other code answers 400 invalid_query naming the Premium read. On a Premium transcript the account owns, each other language costs the Premium rate, 300 credits per started 15 minutes, once per account and language, and is free after that. While a translation runs the read answers 503 transcript_fetching with Retry-After and progress, and charges nothing. languages[] lists every caption track the video offers." }, "required": false, - "description": "Comma-separated caption language priority list, at most 5, tried in order (e.g. \"de,en\"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers.", + "description": "The language to read the transcript in, as a code (e.g. \"es\") or a comma-separated priority list of at most 5 (e.g. \"de,en\"); asr picks the first automatic caption track and asr- a specific one. Every transcript has a base language, base_language in the response: the language of the original caption track, or the language a Premium transcript was spoken and transcribed in. language= with no value returns the base transcript; so does a code in the base language. Omitted, the account default applies (en unless changed), falling back to the base transcript. When no code in the list matches, the transcript is translated into the first code, line for line with the same timestamps and speakers. Captions translate only into the languages YouTube makes available for the video (translation_languages in the response, or GET /v1/videos/{video_id}/captions), billed as a caption read of the window, 4 credits per started 15 minutes; any other code answers 400 invalid_query naming the Premium read. On a Premium transcript the account owns, each other language costs the Premium rate, 300 credits per started 15 minutes, once per account and language, and is free after that. While a translation runs the read answers 503 transcript_fetching with Retry-After and progress, and charges nothing. languages[] lists every caption track the video offers.", "name": "language", "in": "query" }, @@ -10810,7 +10872,7 @@ ], "responses": { "200": { - "description": "state ready: the transcript (video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note). state failed (Premium only): the last Premium transcript for this video failed; job and last_attempt say why, nothing was charged, and retry=true starts a new one.", + "description": "state ready: the transcript (video, quality, source, language, base_language, languages, translation_languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note). state failed (Premium only): the last Premium transcript for this video failed; job and last_attempt say why, nothing was charged, and retry=true starts a new one.", "headers": { "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" @@ -10929,7 +10991,7 @@ "$ref": "#/components/responses/ServerError" }, "503": { - "description": "transcript_fetching. The caption track is being fetched now. Retry-After and retry_after_seconds carry the wait; nothing was charged.", + "description": "transcript_fetching. The caption track is being fetched, or the transcript translated, now. Retry-After and retry_after_seconds carry the wait; a translation also carries progress (done and total lines). Nothing was charged by this call.", "headers": { "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" diff --git a/pyproject.toml b/pyproject.toml index b6ff121..f9f4ffa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "arcmira" dynamic = ["version"] -description = "YouTube and podcast transcript search with the official Arcmira Python SDK. Find timestamped quotes, mentions, sponsors and recommendations." +description = "YouTube transcript search and monitoring for your AI: exact quotes, full transcripts and speakers." readme = "README.md" license = "Apache-2.0" license-files = ["LICENSE"] diff --git a/reference.md b/reference.md index 23d0ac9..df059f0 100644 --- a/reference.md +++ b/reference.md @@ -370,7 +370,7 @@ client.entities.get(
-Mentions in a lookback window of days (7 to 365, default 30) against the same number of days before it, an absolute-delta verdict on that window (accelerating, flat, fading, none), the newest media date, and the top shows in the window. Pass the window the rest of your answer uses: days=90 for a 90-day answer. The fixed 7, 30 and 90 day counts stay on every card. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic for the window; otherwise that field is absent and access names the gate. On a plan with a freshness gate, the window ends at the cutoff and the card carries freshness, unlock, and a note that says where results stop. Uses 10 credits from your plan at any window, the default included. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. +Compare an entity's mention volume with the preceding period. Returns the counts, absolute and percentage change, momentum verdict, latest source date, and top shows. Choose a rolling window of 7 to 365 days with `days` (default 30). The preceding period is the same number of days before the window, and the verdict (accelerating, flat, fading, none) reads the absolute change. Pass the window the rest of your answer uses: days=90 for a 90-day answer. The fixed 7, 30 and 90 day counts stay on every card. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic for the window; otherwise that field is absent and access names the gate. On a plan with a freshness gate, the window ends at the cutoff and the card carries freshness, unlock, and a note that says where results stop. Uses 10 credits from your plan at any window, the default included. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.
@@ -452,7 +452,7 @@ client.entities.momentum(
-Cursor-paginated mentions filtered by entity (entity_id is required; resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter), channel (channel_id), text query, sentiment, appearance flag, and publication window [after, before). The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing mentions remain live. Positions are start_seconds and end_seconds (integer seconds; 0 means full episode). is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. +Find where a person, company, product, or topic is mentioned in indexed videos and podcasts. For a person, set `is_appearance` to true to find media appearances, false for mentions by others, or omit it for both. Results include a description, source video, and available timestamps. Cursor-paginated, filtered by entity (`entity_id` is required; resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter), channel (`channel_id`), text query, sentiment, appearance flag, and publication window [after, before). The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing mentions remain live. Positions are start_seconds and end_seconds (integer seconds; 0 means full episode). is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan.
@@ -863,7 +863,7 @@ client.recommendations.list(
-Attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status "logged"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alerts: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert id, and every referenced alert must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with nothing to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. experience feedback says how a task went as a whole rather than correcting a result: it requires category and notes, refuses corrections (400 invalid_feedback_request), and needs no query. category and mcp_call_id, when sent, are recorded in the stored query. +Help shape Arcmira. Report a wrong result, request sources or capabilities, or tell us what would make your workflow more useful. To correct a result, attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status "logged"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alerts: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert id, and every referenced alert must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with nothing to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. experience feedback says how a task went as a whole rather than correcting a result: it requires category and notes, refuses corrections (400 invalid_feedback_request), and needs no query. category and mcp_call_id, when sent, are recorded in the stored query.
@@ -1099,7 +1099,7 @@ client.feedback.get(
-Search indexed YouTube and podcast transcripts for short spoken slices. Each result includes spoken text, a watch URL, and a publish date. Scope with channel_ids (or channel) and entity_ids (a person id filters to that person's appearances); narrow to passages about entities with about, to a speaker with by, and to sponsored, organic or mention passages with kind. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. An after later than the plan's freshness gate is refused with freshness_requires_paid rather than widened. A window the gate cut carries freshness (delay_days, served_through), unlock, and a note that says where results on this plan stop. Uses 4 credits per chunk returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. +Search indexed YouTube and podcast transcripts for passages matching a word or phrase. Returns passage text, source metadata, publication dates, and timestamped watch links. Narrow to passages about an entity with `about`, or passages where a person speaks with `by`. Scope with `channel_ids` (or `channel`) and `entity_ids` (a person id filters to that person's appearances). Filter to sponsored, organic or mention passages with `kind`. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. An after later than the plan's freshness gate is refused with freshness_requires_paid rather than widened. A window the gate cut carries freshness (delay_days, served_through), unlock, and a note that says where results on this plan stop. Uses 4 credits per chunk returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.
@@ -1180,7 +1180,7 @@ client.transcripts.search(
-**by:** `typing.Optional[str]` — Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people says the query words (each line of a chunk is labeled with its speaker); a non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. +**by:** `typing.Optional[str]` — Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people speaks the query words. A passage can include other speakers' lines; each line with a known speaker starts with that speaker's name. A non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve.
@@ -1188,7 +1188,7 @@ client.transcripts.search(
-**kind:** `typing.Optional[str]` — Comma-separated passage classes: sponsored, organic, mention. Combine with about to read what was said about a brand in ad reads or in organic talk. +**kind:** `typing.Optional[str]` — Comma-separated passage classes: sponsored, organic, mention. Combine with about to read what was said about a brand in ad reads or in organic talk. organic and mention leave out every passage tagged as an ad read, including one that also names other brands; add sponsored to include them.
@@ -1252,7 +1252,7 @@ client.transcripts.search(
-Caption reads use 4 credits per started 15 minutes. quality=premium is one read. An owned transcript answers 200 ready at no charge. Otherwise this call starts a Premium transcript of the whole video, 300 credits per started 15 minutes, using credits from the account's plan first and then the account's on-demand budget up to its limit, and answers 202 pending with the job and Retry-After until the transcript is ready. Read again after Retry-After; repeated reads join the same job and never charge twice. When the last Premium transcript for the video failed, the read answers 200 state failed with the job and last_attempt and charges nothing; retry=true starts a new one. When the plan or the budget blocks, 403 paid_plan_required (with unlock) or 402 quota_exceeded or spend_limit_exceeded carries the price in quote and the limit it met in limit, and nothing is charged. A default-premium account with nothing owned reads captions with a note. On captions, start and end set the billed window: 4 credits per started 15 minutes of end minus start. On Premium they only trim the returned content, and the charge is the whole video. language selects caption tracks, timestamps=false returns paragraphs. Premium lines carry speaker and index, and the body carries speakers and revision. +Caption reads use 4 credits per started 15 minutes. quality=premium is one read. An owned transcript answers 200 ready at no charge. Otherwise this call starts a Premium transcript of the whole video, 300 credits per started 15 minutes, using credits from the account's plan first and then the account's on-demand budget up to its limit, and answers 202 pending with the job and Retry-After until the transcript is ready. Read again after Retry-After; repeated reads join the same job and never charge twice. When the last Premium transcript for the video failed, the read answers 200 state failed with the job and last_attempt and charges nothing; retry=true starts a new one. When the plan or the budget blocks, 403 paid_plan_required (with unlock) or 402 quota_exceeded or spend_limit_exceeded carries the price in quote and the limit it met in limit, and nothing is charged. A default-premium account with nothing owned reads captions with a note. On captions, start and end set the billed window: 4 credits per started 15 minutes of end minus start. On Premium they only trim the returned content, and the charge is the whole video. language selects caption tracks, timestamps=false returns paragraphs. Premium lines carry speaker and index, and the body carries speakers and revision. language picks the language: language= returns the base transcript (base_language), and a language the transcript is not in returns it translated, line for line with the same timestamps and speakers. Captions offer only the languages YouTube makes available for the video (translation_languages), billed as a caption read; any other language is Premium: an owned Premium transcript in another language costs the Premium rate once per account and language.
@@ -1309,7 +1309,7 @@ client.transcripts.get(
-**language:** `typing.Optional[str]` — Comma-separated caption language priority list, at most 5, tried in order (e.g. "de,en"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers. +**language:** `typing.Optional[str]` — The language to read the transcript in, as a code (e.g. "es") or a comma-separated priority list of at most 5 (e.g. "de,en"); asr picks the first automatic caption track and asr- a specific one. Every transcript has a base language, base_language in the response: the language of the original caption track, or the language a Premium transcript was spoken and transcribed in. language= with no value returns the base transcript; so does a code in the base language. Omitted, the account default applies (en unless changed), falling back to the base transcript. When no code in the list matches, the transcript is translated into the first code, line for line with the same timestamps and speakers. Captions translate only into the languages YouTube makes available for the video (translation_languages in the response, or GET /v1/videos/{video_id}/captions), billed as a caption read of the window, 4 credits per started 15 minutes; any other code answers 400 invalid_query naming the Premium read. On a Premium transcript the account owns, each other language costs the Premium rate, 300 credits per started 15 minutes, once per account and language, and is free after that. While a translation runs the read answers 503 transcript_fetching with Retry-After and progress, and charges nothing. languages[] lists every caption track the video offers.
@@ -1542,7 +1542,7 @@ client.transcripts.list_requests()
-How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; channel backfill is not available yet. Free; uses no credits. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. +How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; to ask for a channel, send feedback with its channel id. Free; uses no credits. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.
@@ -2526,7 +2526,7 @@ client.channels.sponsors.list(
-The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing videos remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Uses 4 credits per video returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. +The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing videos remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; this route cannot add a channel. Uses 4 credits per video returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server.
diff --git a/src/arcmira/__init__.py b/src/arcmira/__init__.py index 86a3279..891abb7 100644 --- a/src/arcmira/__init__.py +++ b/src/arcmira/__init__.py @@ -246,6 +246,7 @@ TranscriptResult_Pending, TranscriptResult_Ready, TranscriptSearchChunk, + TranscriptSearchChunkMatch, TranscriptSearchResponse, TranscriptSearchResponseAccess, TranscriptSearchResponseAccessDetails, @@ -582,6 +583,7 @@ "TranscriptResult_Pending": ".types", "TranscriptResult_Ready": ".types", "TranscriptSearchChunk": ".types", + "TranscriptSearchChunkMatch": ".types", "TranscriptSearchResponse": ".types", "TranscriptSearchResponseAccess": ".types", "TranscriptSearchResponseAccessDetails": ".types", @@ -913,6 +915,7 @@ def __dir__(): "TranscriptResult_Pending", "TranscriptResult_Ready", "TranscriptSearchChunk", + "TranscriptSearchChunkMatch", "TranscriptSearchResponse", "TranscriptSearchResponseAccess", "TranscriptSearchResponseAccessDetails", diff --git a/src/arcmira/_package.py b/src/arcmira/_package.py index 798b6cc..e55b214 100644 --- a/src/arcmira/_package.py +++ b/src/arcmira/_package.py @@ -1,5 +1,5 @@ # Written by scripts/install-generated.py from VERSION. -__version__ = '0.6.2' +__version__ = '0.6.3' homepage = "https://arcmira.com" docs = "https://arcmira.com/docs" api_base = "https://api.arcmira.com/v1" diff --git a/src/arcmira/channels/client.py b/src/arcmira/channels/client.py index 6447b53..4b5fb26 100644 --- a/src/arcmira/channels/client.py +++ b/src/arcmira/channels/client.py @@ -36,7 +36,7 @@ def coverage( self, channel_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> ChannelCoverageResponse: """ - How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; channel backfill is not available yet. Free; uses no credits. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; to ask for a channel, send feedback with its channel id. Free; uses no credits. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- @@ -104,7 +104,7 @@ async def coverage( self, channel_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> ChannelCoverageResponse: """ - How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; channel backfill is not available yet. Free; uses no credits. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; to ask for a channel, send feedback with its channel id. Free; uses no credits. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- diff --git a/src/arcmira/channels/raw_client.py b/src/arcmira/channels/raw_client.py index 448ff8c..8598736 100644 --- a/src/arcmira/channels/raw_client.py +++ b/src/arcmira/channels/raw_client.py @@ -29,7 +29,7 @@ def coverage( self, channel_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> HttpResponse[ChannelCoverageResponse]: """ - How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; channel backfill is not available yet. Free; uses no credits. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; to ask for a channel, send feedback with its channel id. Free; uses no credits. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- @@ -143,7 +143,7 @@ async def coverage( self, channel_id: str, *, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[ChannelCoverageResponse]: """ - How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; channel backfill is not available yet. Free; uses no credits. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + How many videos of a YouTube channel are searchable, the newest publish date among them, and the split by transcript source class. Call it when a search or mention lookup came back empty, before telling anyone we do not cover a show, and cite indexed_through as the as-of date for mentions and search_indexed_through for transcript search. It cannot request indexing; to ask for a channel, send feedback with its channel id. Free; uses no credits. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- diff --git a/src/arcmira/channels/videos/client.py b/src/arcmira/channels/videos/client.py index 6f2889a..14358ce 100644 --- a/src/arcmira/channels/videos/client.py +++ b/src/arcmira/channels/videos/client.py @@ -36,7 +36,7 @@ def list( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse]: """ - The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing videos remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Uses 4 credits per video returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing videos remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; this route cannot add a channel. Uses 4 credits per video returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- @@ -110,7 +110,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse]: """ - The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing videos remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Uses 4 credits per video returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing videos remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; this route cannot add a channel. Uses 4 credits per video returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- diff --git a/src/arcmira/channels/videos/raw_client.py b/src/arcmira/channels/videos/raw_client.py index e6c03ac..90348b2 100644 --- a/src/arcmira/channels/videos/raw_client.py +++ b/src/arcmira/channels/videos/raw_client.py @@ -38,7 +38,7 @@ def list( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse]: """ - The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing videos remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Uses 4 credits per video returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing videos remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; this route cannot add a channel. Uses 4 credits per video returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- @@ -199,7 +199,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[ChannelVideosResponseEpisodesItem, ChannelVideosResponse]: """ - The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing videos remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; channel backfill is not available yet. Uses 4 credits per video returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + The indexed videos of a YouTube channel, newest first, each with its video_id, title, publish date, duration, view count, and watch_url on arcmira.com. Pass next_cursor as cursor to continue. The signed token binds the route, filters, caller and visibility; invalid or old tokens return invalid_cursor. A first-page media ID fence excludes later insertions, including old-date backfills; edits and deletions to existing videos remain live. Call it for the latest or most recent episode of a show, or to list what a show published in a window, then pass a video_id to GET /v1/mentions/counts video_ids for what that episode mentions or to GET /v1/transcripts/{video_id} to read it. indexed_through is the newest date we hold for the channel. An empty list means nothing is indexed; this route cannot add a channel. Uses 4 credits per video returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- diff --git a/src/arcmira/core/client_wrapper.py b/src/arcmira/core/client_wrapper.py index ec87430..935a211 100644 --- a/src/arcmira/core/client_wrapper.py +++ b/src/arcmira/core/client_wrapper.py @@ -33,7 +33,7 @@ def get_headers(self) -> typing.Dict[str, str]: import platform headers: typing.Dict[str, str] = { - "User-Agent": "arcmira/0.6.2", + "User-Agent": "arcmira/0.6.3", "X-Fern-Language": "Python", "X-Fern-Runtime": f"python/{platform.python_version()}", "X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}", diff --git a/src/arcmira/entities/client.py b/src/arcmira/entities/client.py index 3009d05..c029c3f 100644 --- a/src/arcmira/entities/client.py +++ b/src/arcmira/entities/client.py @@ -111,7 +111,7 @@ def momentum( self, id: str, *, days: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None ) -> EntityMomentumResponse: """ - Mentions in a lookback window of days (7 to 365, default 30) against the same number of days before it, an absolute-delta verdict on that window (accelerating, flat, fading, none), the newest media date, and the top shows in the window. Pass the window the rest of your answer uses: days=90 for a 90-day answer. The fixed 7, 30 and 90 day counts stay on every card. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic for the window; otherwise that field is absent and access names the gate. On a plan with a freshness gate, the window ends at the cutoff and the card carries freshness, unlock, and a note that says where results stop. Uses 10 credits from your plan at any window, the default included. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + Compare an entity's mention volume with the preceding period. Returns the counts, absolute and percentage change, momentum verdict, latest source date, and top shows. Choose a rolling window of 7 to 365 days with `days` (default 30). The preceding period is the same number of days before the window, and the verdict (accelerating, flat, fading, none) reads the absolute change. Pass the window the rest of your answer uses: days=90 for a 90-day answer. The fixed 7, 30 and 90 day counts stay on every card. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic for the window; otherwise that field is absent and access names the gate. On a plan with a freshness gate, the window ends at the cutoff and the card carries freshness, unlock, and a note that says where results stop. Uses 10 credits from your plan at any window, the default included. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- @@ -260,7 +260,7 @@ async def momentum( self, id: str, *, days: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None ) -> EntityMomentumResponse: """ - Mentions in a lookback window of days (7 to 365, default 30) against the same number of days before it, an absolute-delta verdict on that window (accelerating, flat, fading, none), the newest media date, and the top shows in the window. Pass the window the rest of your answer uses: days=90 for a 90-day answer. The fixed 7, 30 and 90 day counts stay on every card. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic for the window; otherwise that field is absent and access names the gate. On a plan with a freshness gate, the window ends at the cutoff and the card carries freshness, unlock, and a note that says where results stop. Uses 10 credits from your plan at any window, the default included. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + Compare an entity's mention volume with the preceding period. Returns the counts, absolute and percentage change, momentum verdict, latest source date, and top shows. Choose a rolling window of 7 to 365 days with `days` (default 30). The preceding period is the same number of days before the window, and the verdict (accelerating, flat, fading, none) reads the absolute change. Pass the window the rest of your answer uses: days=90 for a 90-day answer. The fixed 7, 30 and 90 day counts stay on every card. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic for the window; otherwise that field is absent and access names the gate. On a plan with a freshness gate, the window ends at the cutoff and the card carries freshness, unlock, and a note that says where results stop. Uses 10 credits from your plan at any window, the default included. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- diff --git a/src/arcmira/entities/raw_client.py b/src/arcmira/entities/raw_client.py index d79d6cc..9ea6cd6 100644 --- a/src/arcmira/entities/raw_client.py +++ b/src/arcmira/entities/raw_client.py @@ -283,7 +283,7 @@ def momentum( self, id: str, *, days: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None ) -> HttpResponse[EntityMomentumResponse]: """ - Mentions in a lookback window of days (7 to 365, default 30) against the same number of days before it, an absolute-delta verdict on that window (accelerating, flat, fading, none), the newest media date, and the top shows in the window. Pass the window the rest of your answer uses: days=90 for a 90-day answer. The fixed 7, 30 and 90 day counts stay on every card. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic for the window; otherwise that field is absent and access names the gate. On a plan with a freshness gate, the window ends at the cutoff and the card carries freshness, unlock, and a note that says where results stop. Uses 10 credits from your plan at any window, the default included. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + Compare an entity's mention volume with the preceding period. Returns the counts, absolute and percentage change, momentum verdict, latest source date, and top shows. Choose a rolling window of 7 to 365 days with `days` (default 30). The preceding period is the same number of days before the window, and the verdict (accelerating, flat, fading, none) reads the absolute change. Pass the window the rest of your answer uses: days=90 for a 90-day answer. The fixed 7, 30 and 90 day counts stay on every card. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic for the window; otherwise that field is absent and access names the gate. On a plan with a freshness gate, the window ends at the cutoff and the card carries freshness, unlock, and a note that says where results stop. Uses 10 credits from your plan at any window, the default included. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- @@ -664,7 +664,7 @@ async def momentum( self, id: str, *, days: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None ) -> AsyncHttpResponse[EntityMomentumResponse]: """ - Mentions in a lookback window of days (7 to 365, default 30) against the same number of days before it, an absolute-delta verdict on that window (accelerating, flat, fading, none), the newest media date, and the top shows in the window. Pass the window the rest of your answer uses: days=90 for a 90-day answer. The fixed 7, 30 and 90 day counts stay on every card. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic for the window; otherwise that field is absent and access names the gate. On a plan with a freshness gate, the window ends at the cutoff and the card carries freshness, unlock, and a note that says where results stop. Uses 10 credits from your plan at any window, the default included. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + Compare an entity's mention volume with the preceding period. Returns the counts, absolute and percentage change, momentum verdict, latest source date, and top shows. Choose a rolling window of 7 to 365 days with `days` (default 30). The preceding period is the same number of days before the window, and the verdict (accelerating, flat, fading, none) reads the absolute change. Pass the window the rest of your answer uses: days=90 for a 90-day answer. The fixed 7, 30 and 90 day counts stay on every card. It counts the shows we index, not the whole internet, and it is a count, not a score. On a Pro+ plan the card also carries paid_vs_organic for the window; otherwise that field is absent and access names the gate. On a plan with a freshness gate, the window ends at the cutoff and the card carries freshness, unlock, and a note that says where results stop. Uses 10 credits from your plan at any window, the default included. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- diff --git a/src/arcmira/feedback/client.py b/src/arcmira/feedback/client.py index f9cd42b..6b9d71b 100644 --- a/src/arcmira/feedback/client.py +++ b/src/arcmira/feedback/client.py @@ -49,7 +49,7 @@ def submit( request_options: typing.Optional[RequestOptions] = None, ) -> FeedbackResponse: """ - Attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status "logged"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alerts: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert id, and every referenced alert must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with nothing to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. experience feedback says how a task went as a whole rather than correcting a result: it requires category and notes, refuses corrections (400 invalid_feedback_request), and needs no query. category and mcp_call_id, when sent, are recorded in the stored query. + Help shape Arcmira. Report a wrong result, request sources or capabilities, or tell us what would make your workflow more useful. To correct a result, attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status "logged"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alerts: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert id, and every referenced alert must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with nothing to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. experience feedback says how a task went as a whole rather than correcting a result: it requires category and notes, refuses corrections (400 invalid_feedback_request), and needs no query. category and mcp_call_id, when sent, are recorded in the stored query. Parameters ---------- @@ -188,7 +188,7 @@ async def submit( request_options: typing.Optional[RequestOptions] = None, ) -> FeedbackResponse: """ - Attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status "logged"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alerts: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert id, and every referenced alert must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with nothing to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. experience feedback says how a task went as a whole rather than correcting a result: it requires category and notes, refuses corrections (400 invalid_feedback_request), and needs no query. category and mcp_call_id, when sent, are recorded in the stored query. + Help shape Arcmira. Report a wrong result, request sources or capabilities, or tell us what would make your workflow more useful. To correct a result, attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status "logged"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alerts: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert id, and every referenced alert must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with nothing to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. experience feedback says how a task went as a whole rather than correcting a result: it requires category and notes, refuses corrections (400 invalid_feedback_request), and needs no query. category and mcp_call_id, when sent, are recorded in the stored query. Parameters ---------- diff --git a/src/arcmira/feedback/raw_client.py b/src/arcmira/feedback/raw_client.py index 3441465..4730c38 100644 --- a/src/arcmira/feedback/raw_client.py +++ b/src/arcmira/feedback/raw_client.py @@ -53,7 +53,7 @@ def submit( request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[FeedbackResponse]: """ - Attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status "logged"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alerts: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert id, and every referenced alert must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with nothing to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. experience feedback says how a task went as a whole rather than correcting a result: it requires category and notes, refuses corrections (400 invalid_feedback_request), and needs no query. category and mcp_call_id, when sent, are recorded in the stored query. + Help shape Arcmira. Report a wrong result, request sources or capabilities, or tell us what would make your workflow more useful. To correct a result, attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status "logged"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alerts: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert id, and every referenced alert must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with nothing to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. experience feedback says how a task went as a whole rather than correcting a result: it requires category and notes, refuses corrections (400 invalid_feedback_request), and needs no query. category and mcp_call_id, when sent, are recorded in the stored query. Parameters ---------- @@ -351,7 +351,7 @@ async def submit( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[FeedbackResponse]: """ - Attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status "logged"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alerts: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert id, and every referenced alert must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with nothing to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. experience feedback says how a task went as a whole rather than correcting a result: it requires category and notes, refuses corrections (400 invalid_feedback_request), and needs no query. category and mcp_call_id, when sent, are recorded in the stored query. + Help shape Arcmira. Report a wrong result, request sources or capabilities, or tell us what would make your workflow more useful. To correct a result, attach corrections to the exact query you ran: pass the feedback type, the query object you sent, and optional per-item corrections. Public submissions are recorded for human review (status "logged"); nothing is auto-applied. Read the review status back later via GET /v1/feedback/{feedback_id}. recommendations and channel_sponsors feedback types require a Pro+ plan; every other type needs read. monitor_alert feedback targets fired alerts: query carries monitor_id and/or tracker_id and/or alert_id, corrections target the alert id, and every referenced alert must belong to the caller (otherwise 404 alert_not_found). missed_alert corrections are expectations with nothing to target: omit the correction id and put { source_url, approximate_timestamp_seconds?, entity_id? } in suggested_change. delivery_issue corrections may carry { channel } in suggested_change. experience feedback says how a task went as a whole rather than correcting a result: it requires category and notes, refuses corrections (400 invalid_feedback_request), and needs no query. category and mcp_call_id, when sent, are recorded in the stored query. Parameters ---------- diff --git a/src/arcmira/mentions/client.py b/src/arcmira/mentions/client.py index 20c92e1..168ac8b 100644 --- a/src/arcmira/mentions/client.py +++ b/src/arcmira/mentions/client.py @@ -45,7 +45,7 @@ def list( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Mention, MentionListResponse]: """ - Cursor-paginated mentions filtered by entity (entity_id is required; resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter), channel (channel_id), text query, sentiment, appearance flag, and publication window [after, before). The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing mentions remain live. Positions are start_seconds and end_seconds (integer seconds; 0 means full episode). is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. + Find where a person, company, product, or topic is mentioned in indexed videos and podcasts. For a person, set `is_appearance` to true to find media appearances, false for mentions by others, or omit it for both. Results include a description, source video, and available timestamps. Cursor-paginated, filtered by entity (`entity_id` is required; resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter), channel (`channel_id`), text query, sentiment, appearance flag, and publication window [after, before). The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing mentions remain live. Positions are start_seconds and end_seconds (integer seconds; 0 means full episode). is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. Parameters ---------- @@ -216,7 +216,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Mention, MentionListResponse]: """ - Cursor-paginated mentions filtered by entity (entity_id is required; resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter), channel (channel_id), text query, sentiment, appearance flag, and publication window [after, before). The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing mentions remain live. Positions are start_seconds and end_seconds (integer seconds; 0 means full episode). is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. + Find where a person, company, product, or topic is mentioned in indexed videos and podcasts. For a person, set `is_appearance` to true to find media appearances, false for mentions by others, or omit it for both. Results include a description, source video, and available timestamps. Cursor-paginated, filtered by entity (`entity_id` is required; resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter), channel (`channel_id`), text query, sentiment, appearance flag, and publication window [after, before). The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing mentions remain live. Positions are start_seconds and end_seconds (integer seconds; 0 means full episode). is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. Parameters ---------- diff --git a/src/arcmira/mentions/raw_client.py b/src/arcmira/mentions/raw_client.py index 3b3735d..e712d47 100644 --- a/src/arcmira/mentions/raw_client.py +++ b/src/arcmira/mentions/raw_client.py @@ -47,7 +47,7 @@ def list( request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[Mention, MentionListResponse]: """ - Cursor-paginated mentions filtered by entity (entity_id is required; resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter), channel (channel_id), text query, sentiment, appearance flag, and publication window [after, before). The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing mentions remain live. Positions are start_seconds and end_seconds (integer seconds; 0 means full episode). is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. + Find where a person, company, product, or topic is mentioned in indexed videos and podcasts. For a person, set `is_appearance` to true to find media appearances, false for mentions by others, or omit it for both. Results include a description, source video, and available timestamps. Cursor-paginated, filtered by entity (`entity_id` is required; resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter), channel (`channel_id`), text query, sentiment, appearance flag, and publication window [after, before). The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing mentions remain live. Positions are start_seconds and end_seconds (integer seconds; 0 means full episode). is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. Parameters ---------- @@ -395,7 +395,7 @@ async def list( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[Mention, MentionListResponse]: """ - Cursor-paginated mentions filtered by entity (entity_id is required; resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter), channel (channel_id), text query, sentiment, appearance flag, and publication window [after, before). The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing mentions remain live. Positions are start_seconds and end_seconds (integer seconds; 0 means full episode). is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. + Find where a person, company, product, or topic is mentioned in indexed videos and podcasts. For a person, set `is_appearance` to true to find media appearances, false for mentions by others, or omit it for both. Results include a description, source video, and available timestamps. Cursor-paginated, filtered by entity (`entity_id` is required; resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter), channel (`channel_id`), text query, sentiment, appearance flag, and publication window [after, before). The signed continuation binds the route, filters, caller and visibility; invalid or old cursors return invalid_cursor. A first-page ID fence excludes later insertions, including old-date backfills. Edits and deletions to existing mentions remain live. Positions are start_seconds and end_seconds (integer seconds; 0 means full episode). is_appearance filtering applies to person entities only; passing is_appearance=true for any other type returns a 400 (appearances_person_only). details=full attaches per-mention commercial recommendations and requires a Pro+ plan. Parameters ---------- diff --git a/src/arcmira/transcripts/client.py b/src/arcmira/transcripts/client.py index db63917..25fec44 100644 --- a/src/arcmira/transcripts/client.py +++ b/src/arcmira/transcripts/client.py @@ -47,7 +47,7 @@ def search( request_options: typing.Optional[RequestOptions] = None, ) -> TranscriptSearchResponse: """ - Search indexed YouTube and podcast transcripts for short spoken slices. Each result includes spoken text, a watch URL, and a publish date. Scope with channel_ids (or channel) and entity_ids (a person id filters to that person's appearances); narrow to passages about entities with about, to a speaker with by, and to sponsored, organic or mention passages with kind. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. An after later than the plan's freshness gate is refused with freshness_requires_paid rather than widened. A window the gate cut carries freshness (delay_days, served_through), unlock, and a note that says where results on this plan stop. Uses 4 credits per chunk returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + Search indexed YouTube and podcast transcripts for passages matching a word or phrase. Returns passage text, source metadata, publication dates, and timestamped watch links. Narrow to passages about an entity with `about`, or passages where a person speaks with `by`. Scope with `channel_ids` (or `channel`) and `entity_ids` (a person id filters to that person's appearances). Filter to sponsored, organic or mention passages with `kind`. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. An after later than the plan's freshness gate is refused with freshness_requires_paid rather than widened. A window the gate cut carries freshness (delay_days, served_through), unlock, and a note that says where results on this plan stop. Uses 4 credits per chunk returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- @@ -67,10 +67,10 @@ def search( Comma-separated entity ids (ent_{n}), at most 8. Only passages about these entities: excerpt pins, exact-name mentions and ad verdicts. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. by : typing.Optional[str] - Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people says the query words (each line of a chunk is labeled with its speaker); a non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people speaks the query words. A passage can include other speakers' lines; each line with a known speaker starts with that speaker's name. A non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. kind : typing.Optional[str] - Comma-separated passage classes: sponsored, organic, mention. Combine with about to read what was said about a brand in ad reads or in organic talk. + Comma-separated passage classes: sponsored, organic, mention. Combine with about to read what was said about a brand in ad reads or in organic talk. organic and mention leave out every passage tagged as an ad read, including one that also names other brands; add sponsored to include them. after : typing.Optional[str] Only media published at or after this instant. An after later than your plan's freshness gate is refused with freshness_requires_paid rather than widened. An ISO 8601 date (2026-09-01) or datetime with offset (2026-09-01T00:00:00Z), read in UTC. The window is half-open: after is inclusive, before is exclusive. @@ -133,7 +133,7 @@ def get( request_options: typing.Optional[RequestOptions] = None, ) -> TranscriptResult: """ - Caption reads use 4 credits per started 15 minutes. quality=premium is one read. An owned transcript answers 200 ready at no charge. Otherwise this call starts a Premium transcript of the whole video, 300 credits per started 15 minutes, using credits from the account's plan first and then the account's on-demand budget up to its limit, and answers 202 pending with the job and Retry-After until the transcript is ready. Read again after Retry-After; repeated reads join the same job and never charge twice. When the last Premium transcript for the video failed, the read answers 200 state failed with the job and last_attempt and charges nothing; retry=true starts a new one. When the plan or the budget blocks, 403 paid_plan_required (with unlock) or 402 quota_exceeded or spend_limit_exceeded carries the price in quote and the limit it met in limit, and nothing is charged. A default-premium account with nothing owned reads captions with a note. On captions, start and end set the billed window: 4 credits per started 15 minutes of end minus start. On Premium they only trim the returned content, and the charge is the whole video. language selects caption tracks, timestamps=false returns paragraphs. Premium lines carry speaker and index, and the body carries speakers and revision. + Caption reads use 4 credits per started 15 minutes. quality=premium is one read. An owned transcript answers 200 ready at no charge. Otherwise this call starts a Premium transcript of the whole video, 300 credits per started 15 minutes, using credits from the account's plan first and then the account's on-demand budget up to its limit, and answers 202 pending with the job and Retry-After until the transcript is ready. Read again after Retry-After; repeated reads join the same job and never charge twice. When the last Premium transcript for the video failed, the read answers 200 state failed with the job and last_attempt and charges nothing; retry=true starts a new one. When the plan or the budget blocks, 403 paid_plan_required (with unlock) or 402 quota_exceeded or spend_limit_exceeded carries the price in quote and the limit it met in limit, and nothing is charged. A default-premium account with nothing owned reads captions with a note. On captions, start and end set the billed window: 4 credits per started 15 minutes of end minus start. On Premium they only trim the returned content, and the charge is the whole video. language selects caption tracks, timestamps=false returns paragraphs. Premium lines carry speaker and index, and the body carries speakers and revision. language picks the language: language= returns the base transcript (base_language), and a language the transcript is not in returns it translated, line for line with the same timestamps and speakers. Captions offer only the languages YouTube makes available for the video (translation_languages), billed as a caption read; any other language is Premium: an owned Premium transcript in another language costs the Premium rate once per account and language. Parameters ---------- @@ -144,7 +144,7 @@ def get( captions reads creator or automatic captions at 4 credits per started 15 minutes. premium is one read. An owned transcript returns 200 state ready at no charge. Otherwise the read starts a Premium transcript of the whole video, 300 credits per started 15 minutes, using credits from the account's plan first and then the account's on-demand budget up to its limit, and returns 202 state pending with the job until it is ready. When the last Premium transcript for the video failed it answers 200 state failed and starts a new one only with retry=true. 402 quota_exceeded or spend_limit_exceeded and 403 paid_plan_required carry the price in quote. It never substitutes captions. Default captions unless changed in account settings. language : typing.Optional[str] - Comma-separated caption language priority list, at most 5, tried in order (e.g. "de,en"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers. + The language to read the transcript in, as a code (e.g. "es") or a comma-separated priority list of at most 5 (e.g. "de,en"); asr picks the first automatic caption track and asr- a specific one. Every transcript has a base language, base_language in the response: the language of the original caption track, or the language a Premium transcript was spoken and transcribed in. language= with no value returns the base transcript; so does a code in the base language. Omitted, the account default applies (en unless changed), falling back to the base transcript. When no code in the list matches, the transcript is translated into the first code, line for line with the same timestamps and speakers. Captions translate only into the languages YouTube makes available for the video (translation_languages in the response, or GET /v1/videos/{video_id}/captions), billed as a caption read of the window, 4 credits per started 15 minutes; any other code answers 400 invalid_query naming the Premium read. On a Premium transcript the account owns, each other language costs the Premium rate, 300 credits per started 15 minutes, once per account and language, and is free after that. While a translation runs the read answers 503 transcript_fetching with Retry-After and progress, and charges nothing. languages[] lists every caption track the video offers. timestamps : typing.Optional[bool] false returns paragraphs[] of { start, text, speaker? } instead of lines[], for reading rather than citing. Default true. @@ -167,7 +167,7 @@ def get( Returns ------- TranscriptResult - state ready: the transcript (video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note). state failed (Premium only): the last Premium transcript for this video failed; job and last_attempt say why, nothing was charged, and retry=true starts a new one. + state ready: the transcript (video, quality, source, language, base_language, languages, translation_languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note). state failed (Premium only): the last Premium transcript for this video failed; job and last_attempt say why, nothing was charged, and retry=true starts a new one. Examples -------- @@ -305,7 +305,7 @@ async def search( request_options: typing.Optional[RequestOptions] = None, ) -> TranscriptSearchResponse: """ - Search indexed YouTube and podcast transcripts for short spoken slices. Each result includes spoken text, a watch URL, and a publish date. Scope with channel_ids (or channel) and entity_ids (a person id filters to that person's appearances); narrow to passages about entities with about, to a speaker with by, and to sponsored, organic or mention passages with kind. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. An after later than the plan's freshness gate is refused with freshness_requires_paid rather than widened. A window the gate cut carries freshness (delay_days, served_through), unlock, and a note that says where results on this plan stop. Uses 4 credits per chunk returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + Search indexed YouTube and podcast transcripts for passages matching a word or phrase. Returns passage text, source metadata, publication dates, and timestamped watch links. Narrow to passages about an entity with `about`, or passages where a person speaks with `by`. Scope with `channel_ids` (or `channel`) and `entity_ids` (a person id filters to that person's appearances). Filter to sponsored, organic or mention passages with `kind`. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. An after later than the plan's freshness gate is refused with freshness_requires_paid rather than widened. A window the gate cut carries freshness (delay_days, served_through), unlock, and a note that says where results on this plan stop. Uses 4 credits per chunk returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- @@ -325,10 +325,10 @@ async def search( Comma-separated entity ids (ent_{n}), at most 8. Only passages about these entities: excerpt pins, exact-name mentions and ad verdicts. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. by : typing.Optional[str] - Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people says the query words (each line of a chunk is labeled with its speaker); a non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people speaks the query words. A passage can include other speakers' lines; each line with a known speaker starts with that speaker's name. A non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. kind : typing.Optional[str] - Comma-separated passage classes: sponsored, organic, mention. Combine with about to read what was said about a brand in ad reads or in organic talk. + Comma-separated passage classes: sponsored, organic, mention. Combine with about to read what was said about a brand in ad reads or in organic talk. organic and mention leave out every passage tagged as an ad read, including one that also names other brands; add sponsored to include them. after : typing.Optional[str] Only media published at or after this instant. An after later than your plan's freshness gate is refused with freshness_requires_paid rather than widened. An ISO 8601 date (2026-09-01) or datetime with offset (2026-09-01T00:00:00Z), read in UTC. The window is half-open: after is inclusive, before is exclusive. @@ -399,7 +399,7 @@ async def get( request_options: typing.Optional[RequestOptions] = None, ) -> TranscriptResult: """ - Caption reads use 4 credits per started 15 minutes. quality=premium is one read. An owned transcript answers 200 ready at no charge. Otherwise this call starts a Premium transcript of the whole video, 300 credits per started 15 minutes, using credits from the account's plan first and then the account's on-demand budget up to its limit, and answers 202 pending with the job and Retry-After until the transcript is ready. Read again after Retry-After; repeated reads join the same job and never charge twice. When the last Premium transcript for the video failed, the read answers 200 state failed with the job and last_attempt and charges nothing; retry=true starts a new one. When the plan or the budget blocks, 403 paid_plan_required (with unlock) or 402 quota_exceeded or spend_limit_exceeded carries the price in quote and the limit it met in limit, and nothing is charged. A default-premium account with nothing owned reads captions with a note. On captions, start and end set the billed window: 4 credits per started 15 minutes of end minus start. On Premium they only trim the returned content, and the charge is the whole video. language selects caption tracks, timestamps=false returns paragraphs. Premium lines carry speaker and index, and the body carries speakers and revision. + Caption reads use 4 credits per started 15 minutes. quality=premium is one read. An owned transcript answers 200 ready at no charge. Otherwise this call starts a Premium transcript of the whole video, 300 credits per started 15 minutes, using credits from the account's plan first and then the account's on-demand budget up to its limit, and answers 202 pending with the job and Retry-After until the transcript is ready. Read again after Retry-After; repeated reads join the same job and never charge twice. When the last Premium transcript for the video failed, the read answers 200 state failed with the job and last_attempt and charges nothing; retry=true starts a new one. When the plan or the budget blocks, 403 paid_plan_required (with unlock) or 402 quota_exceeded or spend_limit_exceeded carries the price in quote and the limit it met in limit, and nothing is charged. A default-premium account with nothing owned reads captions with a note. On captions, start and end set the billed window: 4 credits per started 15 minutes of end minus start. On Premium they only trim the returned content, and the charge is the whole video. language selects caption tracks, timestamps=false returns paragraphs. Premium lines carry speaker and index, and the body carries speakers and revision. language picks the language: language= returns the base transcript (base_language), and a language the transcript is not in returns it translated, line for line with the same timestamps and speakers. Captions offer only the languages YouTube makes available for the video (translation_languages), billed as a caption read; any other language is Premium: an owned Premium transcript in another language costs the Premium rate once per account and language. Parameters ---------- @@ -410,7 +410,7 @@ async def get( captions reads creator or automatic captions at 4 credits per started 15 minutes. premium is one read. An owned transcript returns 200 state ready at no charge. Otherwise the read starts a Premium transcript of the whole video, 300 credits per started 15 minutes, using credits from the account's plan first and then the account's on-demand budget up to its limit, and returns 202 state pending with the job until it is ready. When the last Premium transcript for the video failed it answers 200 state failed and starts a new one only with retry=true. 402 quota_exceeded or spend_limit_exceeded and 403 paid_plan_required carry the price in quote. It never substitutes captions. Default captions unless changed in account settings. language : typing.Optional[str] - Comma-separated caption language priority list, at most 5, tried in order (e.g. "de,en"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers. + The language to read the transcript in, as a code (e.g. "es") or a comma-separated priority list of at most 5 (e.g. "de,en"); asr picks the first automatic caption track and asr- a specific one. Every transcript has a base language, base_language in the response: the language of the original caption track, or the language a Premium transcript was spoken and transcribed in. language= with no value returns the base transcript; so does a code in the base language. Omitted, the account default applies (en unless changed), falling back to the base transcript. When no code in the list matches, the transcript is translated into the first code, line for line with the same timestamps and speakers. Captions translate only into the languages YouTube makes available for the video (translation_languages in the response, or GET /v1/videos/{video_id}/captions), billed as a caption read of the window, 4 credits per started 15 minutes; any other code answers 400 invalid_query naming the Premium read. On a Premium transcript the account owns, each other language costs the Premium rate, 300 credits per started 15 minutes, once per account and language, and is free after that. While a translation runs the read answers 503 transcript_fetching with Retry-After and progress, and charges nothing. languages[] lists every caption track the video offers. timestamps : typing.Optional[bool] false returns paragraphs[] of { start, text, speaker? } instead of lines[], for reading rather than citing. Default true. @@ -433,7 +433,7 @@ async def get( Returns ------- TranscriptResult - state ready: the transcript (video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note). state failed (Premium only): the last Premium transcript for this video failed; job and last_attempt say why, nothing was charged, and retry=true starts a new one. + state ready: the transcript (video, quality, source, language, base_language, languages, translation_languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note). state failed (Premium only): the last Premium transcript for this video failed; job and last_attempt say why, nothing was charged, and retry=true starts a new one. Examples -------- diff --git a/src/arcmira/transcripts/raw_client.py b/src/arcmira/transcripts/raw_client.py index 29597bc..8b666b8 100644 --- a/src/arcmira/transcripts/raw_client.py +++ b/src/arcmira/transcripts/raw_client.py @@ -51,7 +51,7 @@ def search( request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[TranscriptSearchResponse]: """ - Search indexed YouTube and podcast transcripts for short spoken slices. Each result includes spoken text, a watch URL, and a publish date. Scope with channel_ids (or channel) and entity_ids (a person id filters to that person's appearances); narrow to passages about entities with about, to a speaker with by, and to sponsored, organic or mention passages with kind. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. An after later than the plan's freshness gate is refused with freshness_requires_paid rather than widened. A window the gate cut carries freshness (delay_days, served_through), unlock, and a note that says where results on this plan stop. Uses 4 credits per chunk returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + Search indexed YouTube and podcast transcripts for passages matching a word or phrase. Returns passage text, source metadata, publication dates, and timestamped watch links. Narrow to passages about an entity with `about`, or passages where a person speaks with `by`. Scope with `channel_ids` (or `channel`) and `entity_ids` (a person id filters to that person's appearances). Filter to sponsored, organic or mention passages with `kind`. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. An after later than the plan's freshness gate is refused with freshness_requires_paid rather than widened. A window the gate cut carries freshness (delay_days, served_through), unlock, and a note that says where results on this plan stop. Uses 4 credits per chunk returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- @@ -71,10 +71,10 @@ def search( Comma-separated entity ids (ent_{n}), at most 8. Only passages about these entities: excerpt pins, exact-name mentions and ad verdicts. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. by : typing.Optional[str] - Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people says the query words (each line of a chunk is labeled with its speaker); a non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people speaks the query words. A passage can include other speakers' lines; each line with a known speaker starts with that speaker's name. A non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. kind : typing.Optional[str] - Comma-separated passage classes: sponsored, organic, mention. Combine with about to read what was said about a brand in ad reads or in organic talk. + Comma-separated passage classes: sponsored, organic, mention. Combine with about to read what was said about a brand in ad reads or in organic talk. organic and mention leave out every passage tagged as an ad read, including one that also names other brands; add sponsored to include them. after : typing.Optional[str] Only media published at or after this instant. An after later than your plan's freshness gate is refused with freshness_requires_paid rather than widened. An ISO 8601 date (2026-09-01) or datetime with offset (2026-09-01T00:00:00Z), read in UTC. The window is half-open: after is inclusive, before is exclusive. @@ -235,7 +235,7 @@ def get( request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[TranscriptResult]: """ - Caption reads use 4 credits per started 15 minutes. quality=premium is one read. An owned transcript answers 200 ready at no charge. Otherwise this call starts a Premium transcript of the whole video, 300 credits per started 15 minutes, using credits from the account's plan first and then the account's on-demand budget up to its limit, and answers 202 pending with the job and Retry-After until the transcript is ready. Read again after Retry-After; repeated reads join the same job and never charge twice. When the last Premium transcript for the video failed, the read answers 200 state failed with the job and last_attempt and charges nothing; retry=true starts a new one. When the plan or the budget blocks, 403 paid_plan_required (with unlock) or 402 quota_exceeded or spend_limit_exceeded carries the price in quote and the limit it met in limit, and nothing is charged. A default-premium account with nothing owned reads captions with a note. On captions, start and end set the billed window: 4 credits per started 15 minutes of end minus start. On Premium they only trim the returned content, and the charge is the whole video. language selects caption tracks, timestamps=false returns paragraphs. Premium lines carry speaker and index, and the body carries speakers and revision. + Caption reads use 4 credits per started 15 minutes. quality=premium is one read. An owned transcript answers 200 ready at no charge. Otherwise this call starts a Premium transcript of the whole video, 300 credits per started 15 minutes, using credits from the account's plan first and then the account's on-demand budget up to its limit, and answers 202 pending with the job and Retry-After until the transcript is ready. Read again after Retry-After; repeated reads join the same job and never charge twice. When the last Premium transcript for the video failed, the read answers 200 state failed with the job and last_attempt and charges nothing; retry=true starts a new one. When the plan or the budget blocks, 403 paid_plan_required (with unlock) or 402 quota_exceeded or spend_limit_exceeded carries the price in quote and the limit it met in limit, and nothing is charged. A default-premium account with nothing owned reads captions with a note. On captions, start and end set the billed window: 4 credits per started 15 minutes of end minus start. On Premium they only trim the returned content, and the charge is the whole video. language selects caption tracks, timestamps=false returns paragraphs. Premium lines carry speaker and index, and the body carries speakers and revision. language picks the language: language= returns the base transcript (base_language), and a language the transcript is not in returns it translated, line for line with the same timestamps and speakers. Captions offer only the languages YouTube makes available for the video (translation_languages), billed as a caption read; any other language is Premium: an owned Premium transcript in another language costs the Premium rate once per account and language. Parameters ---------- @@ -246,7 +246,7 @@ def get( captions reads creator or automatic captions at 4 credits per started 15 minutes. premium is one read. An owned transcript returns 200 state ready at no charge. Otherwise the read starts a Premium transcript of the whole video, 300 credits per started 15 minutes, using credits from the account's plan first and then the account's on-demand budget up to its limit, and returns 202 state pending with the job until it is ready. When the last Premium transcript for the video failed it answers 200 state failed and starts a new one only with retry=true. 402 quota_exceeded or spend_limit_exceeded and 403 paid_plan_required carry the price in quote. It never substitutes captions. Default captions unless changed in account settings. language : typing.Optional[str] - Comma-separated caption language priority list, at most 5, tried in order (e.g. "de,en"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers. + The language to read the transcript in, as a code (e.g. "es") or a comma-separated priority list of at most 5 (e.g. "de,en"); asr picks the first automatic caption track and asr- a specific one. Every transcript has a base language, base_language in the response: the language of the original caption track, or the language a Premium transcript was spoken and transcribed in. language= with no value returns the base transcript; so does a code in the base language. Omitted, the account default applies (en unless changed), falling back to the base transcript. When no code in the list matches, the transcript is translated into the first code, line for line with the same timestamps and speakers. Captions translate only into the languages YouTube makes available for the video (translation_languages in the response, or GET /v1/videos/{video_id}/captions), billed as a caption read of the window, 4 credits per started 15 minutes; any other code answers 400 invalid_query naming the Premium read. On a Premium transcript the account owns, each other language costs the Premium rate, 300 credits per started 15 minutes, once per account and language, and is free after that. While a translation runs the read answers 503 transcript_fetching with Retry-After and progress, and charges nothing. languages[] lists every caption track the video offers. timestamps : typing.Optional[bool] false returns paragraphs[] of { start, text, speaker? } instead of lines[], for reading rather than citing. Default true. @@ -269,7 +269,7 @@ def get( Returns ------- HttpResponse[TranscriptResult] - state ready: the transcript (video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note). state failed (Premium only): the last Premium transcript for this video failed; job and last_attempt say why, nothing was charged, and retry=true starts a new one. + state ready: the transcript (video, quality, source, language, base_language, languages, translation_languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note). state failed (Premium only): the last Premium transcript for this video failed; job and last_attempt say why, nothing was charged, and retry=true starts a new one. """ _response = self._client_wrapper.httpx_client.request( f"v1/transcripts/{encode_path_param(video_id)}", @@ -657,7 +657,7 @@ async def search( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[TranscriptSearchResponse]: """ - Search indexed YouTube and podcast transcripts for short spoken slices. Each result includes spoken text, a watch URL, and a publish date. Scope with channel_ids (or channel) and entity_ids (a person id filters to that person's appearances); narrow to passages about entities with about, to a speaker with by, and to sponsored, organic or mention passages with kind. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. An after later than the plan's freshness gate is refused with freshness_requires_paid rather than widened. A window the gate cut carries freshness (delay_days, served_through), unlock, and a note that says where results on this plan stop. Uses 4 credits per chunk returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. + Search indexed YouTube and podcast transcripts for passages matching a word or phrase. Returns passage text, source metadata, publication dates, and timestamped watch links. Narrow to passages about an entity with `about`, or passages where a person speaks with `by`. Scope with `channel_ids` (or `channel`) and `entity_ids` (a person id filters to that person's appearances). Filter to sponsored, organic or mention passages with `kind`. Every filter takes ids, never names: resolve a name first with GET /v1/entities/resolve, or the call answers 400 id_required naming the parameter. Results carry names beside ids (filters.about, filters.by, chunk about and speakers_by). Use one topic per call. Search results include text on every plan within the plan's publication-date window. Explicitly requesting source=arcmira_premium on a plan without Premium transcripts is refused with filter_requires_paid. An after later than the plan's freshness gate is refused with freshness_requires_paid rather than widened. A window the gate cut carries freshness (delay_days, served_through), unlock, and a note that says where results on this plan stop. Uses 4 credits per chunk returned past the first 5. Every gate is a typed error whose error.unlock.url names the plan that lifts it; pass src=mcp-tool only from the Arcmira MCP server. Parameters ---------- @@ -677,10 +677,10 @@ async def search( Comma-separated entity ids (ent_{n}), at most 8. Only passages about these entities: excerpt pins, exact-name mentions and ad verdicts. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. by : typing.Optional[str] - Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people says the query words (each line of a chunk is labeled with its speaker); a non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. + Comma-separated person ids (ent_{n}), at most 8. Only passages where one of these people speaks the query words. A passage can include other speakers' lines; each line with a known speaker starts with that speaker's name. A non-person id is refused with invalid_query naming its type. Speaker labels cover a minority of shows; an empty result carries a note saying whether the person is labeled anywhere. Ids only: a name answers 400 id_required. Resolve names first with GET /v1/entities/resolve. kind : typing.Optional[str] - Comma-separated passage classes: sponsored, organic, mention. Combine with about to read what was said about a brand in ad reads or in organic talk. + Comma-separated passage classes: sponsored, organic, mention. Combine with about to read what was said about a brand in ad reads or in organic talk. organic and mention leave out every passage tagged as an ad read, including one that also names other brands; add sponsored to include them. after : typing.Optional[str] Only media published at or after this instant. An after later than your plan's freshness gate is refused with freshness_requires_paid rather than widened. An ISO 8601 date (2026-09-01) or datetime with offset (2026-09-01T00:00:00Z), read in UTC. The window is half-open: after is inclusive, before is exclusive. @@ -841,7 +841,7 @@ async def get( request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[TranscriptResult]: """ - Caption reads use 4 credits per started 15 minutes. quality=premium is one read. An owned transcript answers 200 ready at no charge. Otherwise this call starts a Premium transcript of the whole video, 300 credits per started 15 minutes, using credits from the account's plan first and then the account's on-demand budget up to its limit, and answers 202 pending with the job and Retry-After until the transcript is ready. Read again after Retry-After; repeated reads join the same job and never charge twice. When the last Premium transcript for the video failed, the read answers 200 state failed with the job and last_attempt and charges nothing; retry=true starts a new one. When the plan or the budget blocks, 403 paid_plan_required (with unlock) or 402 quota_exceeded or spend_limit_exceeded carries the price in quote and the limit it met in limit, and nothing is charged. A default-premium account with nothing owned reads captions with a note. On captions, start and end set the billed window: 4 credits per started 15 minutes of end minus start. On Premium they only trim the returned content, and the charge is the whole video. language selects caption tracks, timestamps=false returns paragraphs. Premium lines carry speaker and index, and the body carries speakers and revision. + Caption reads use 4 credits per started 15 minutes. quality=premium is one read. An owned transcript answers 200 ready at no charge. Otherwise this call starts a Premium transcript of the whole video, 300 credits per started 15 minutes, using credits from the account's plan first and then the account's on-demand budget up to its limit, and answers 202 pending with the job and Retry-After until the transcript is ready. Read again after Retry-After; repeated reads join the same job and never charge twice. When the last Premium transcript for the video failed, the read answers 200 state failed with the job and last_attempt and charges nothing; retry=true starts a new one. When the plan or the budget blocks, 403 paid_plan_required (with unlock) or 402 quota_exceeded or spend_limit_exceeded carries the price in quote and the limit it met in limit, and nothing is charged. A default-premium account with nothing owned reads captions with a note. On captions, start and end set the billed window: 4 credits per started 15 minutes of end minus start. On Premium they only trim the returned content, and the charge is the whole video. language selects caption tracks, timestamps=false returns paragraphs. Premium lines carry speaker and index, and the body carries speakers and revision. language picks the language: language= returns the base transcript (base_language), and a language the transcript is not in returns it translated, line for line with the same timestamps and speakers. Captions offer only the languages YouTube makes available for the video (translation_languages), billed as a caption read; any other language is Premium: an owned Premium transcript in another language costs the Premium rate once per account and language. Parameters ---------- @@ -852,7 +852,7 @@ async def get( captions reads creator or automatic captions at 4 credits per started 15 minutes. premium is one read. An owned transcript returns 200 state ready at no charge. Otherwise the read starts a Premium transcript of the whole video, 300 credits per started 15 minutes, using credits from the account's plan first and then the account's on-demand budget up to its limit, and returns 202 state pending with the job until it is ready. When the last Premium transcript for the video failed it answers 200 state failed and starts a new one only with retry=true. 402 quota_exceeded or spend_limit_exceeded and 403 paid_plan_required carry the price in quote. It never substitutes captions. Default captions unless changed in account settings. language : typing.Optional[str] - Comma-separated caption language priority list, at most 5, tried in order (e.g. "de,en"). Use asr for the first automatic track and asr- for a specific one. Default en. languages[] in the response lists every track the video offers. + The language to read the transcript in, as a code (e.g. "es") or a comma-separated priority list of at most 5 (e.g. "de,en"); asr picks the first automatic caption track and asr- a specific one. Every transcript has a base language, base_language in the response: the language of the original caption track, or the language a Premium transcript was spoken and transcribed in. language= with no value returns the base transcript; so does a code in the base language. Omitted, the account default applies (en unless changed), falling back to the base transcript. When no code in the list matches, the transcript is translated into the first code, line for line with the same timestamps and speakers. Captions translate only into the languages YouTube makes available for the video (translation_languages in the response, or GET /v1/videos/{video_id}/captions), billed as a caption read of the window, 4 credits per started 15 minutes; any other code answers 400 invalid_query naming the Premium read. On a Premium transcript the account owns, each other language costs the Premium rate, 300 credits per started 15 minutes, once per account and language, and is free after that. While a translation runs the read answers 503 transcript_fetching with Retry-After and progress, and charges nothing. languages[] lists every caption track the video offers. timestamps : typing.Optional[bool] false returns paragraphs[] of { start, text, speaker? } instead of lines[], for reading rather than citing. Default true. @@ -875,7 +875,7 @@ async def get( Returns ------- AsyncHttpResponse[TranscriptResult] - state ready: the transcript (video, quality, source, language, languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note). state failed (Premium only): the last Premium transcript for this video failed; job and last_attempt say why, nothing was charged, and retry=true starts a new one. + state ready: the transcript (video, quality, source, language, base_language, languages, translation_languages, lines or paragraphs, speakers and revision on Premium, rows_billed, as_of, note). state failed (Premium only): the last Premium transcript for this video failed; job and last_attempt say why, nothing was charged, and retry=true starts a new one. """ _response = await self._client_wrapper.httpx_client.request( f"v1/transcripts/{encode_path_param(video_id)}", diff --git a/src/arcmira/types/__init__.py b/src/arcmira/types/__init__.py index 19c52b2..ff31f2b 100644 --- a/src/arcmira/types/__init__.py +++ b/src/arcmira/types/__init__.py @@ -251,6 +251,7 @@ TranscriptResult_Ready, ) from .transcript_search_chunk import TranscriptSearchChunk + from .transcript_search_chunk_match import TranscriptSearchChunkMatch from .transcript_search_response import TranscriptSearchResponse from .transcript_search_response_access import TranscriptSearchResponseAccess from .transcript_search_response_access_details import TranscriptSearchResponseAccessDetails @@ -513,6 +514,7 @@ "TranscriptResult_Pending": ".transcript_result", "TranscriptResult_Ready": ".transcript_result", "TranscriptSearchChunk": ".transcript_search_chunk", + "TranscriptSearchChunkMatch": ".transcript_search_chunk_match", "TranscriptSearchResponse": ".transcript_search_response", "TranscriptSearchResponseAccess": ".transcript_search_response_access", "TranscriptSearchResponseAccessDetails": ".transcript_search_response_access_details", @@ -799,6 +801,7 @@ def __dir__(): "TranscriptResult_Pending", "TranscriptResult_Ready", "TranscriptSearchChunk", + "TranscriptSearchChunkMatch", "TranscriptSearchResponse", "TranscriptSearchResponseAccess", "TranscriptSearchResponseAccessDetails", diff --git a/src/arcmira/types/alert.py b/src/arcmira/types/alert.py index 371699e..d84d868 100644 --- a/src/arcmira/types/alert.py +++ b/src/arcmira/types/alert.py @@ -61,7 +61,7 @@ class Alert(UniversalBaseModel): status: str = pydantic.Field() """ - Delivery status. Values: pending (queued for delivery), sent (delivered), failed (delivery failed; see error_message), skipped (delivery intentionally skipped). + Delivery status. Values: pending (queued for delivery), sending (handed to the destination, answer not yet recorded), sent (delivered), unconfirmed (handed to the destination with no answer recorded; counted as delivered), failed (delivery failed; see error_message), skipped (delivery intentionally skipped). """ final_status: typing.Optional[str] = pydantic.Field(default=None) diff --git a/src/arcmira/types/mention.py b/src/arcmira/types/mention.py index dc0b785..e46b0a6 100644 --- a/src/arcmira/types/mention.py +++ b/src/arcmira/types/mention.py @@ -35,7 +35,7 @@ class Mention(UniversalBaseModel): description: typing.Optional[str] = pydantic.Field(default=None) """ - One-sentence description of the mention context. Null when not generated. + One sentence on why the entity comes up in this video, written to sit under the quoted line. Some mentions read in October 2026 hold the quoted transcript line instead. Null when not generated. """ confidence: typing.Optional[float] = pydantic.Field(default=None) diff --git a/src/arcmira/types/transcript_response.py b/src/arcmira/types/transcript_response.py index fd47e5a..b0b40aa 100644 --- a/src/arcmira/types/transcript_response.py +++ b/src/arcmira/types/transcript_response.py @@ -30,7 +30,12 @@ class TranscriptResponse(UniversalBaseModel): language: str = pydantic.Field() """ - The resolved track code, asr-en style when the track is automatic. + The language of the lines: the resolved track code (asr-en style when the track is automatic) or, when the transcript was translated, the code it was translated into. + """ + + base_language: str = pydantic.Field() + """ + The transcript's own language, as a code (en, es): the original caption track's, or the language a Premium transcript was spoken and transcribed in. language= returns the transcript in it; a language other than base_language means the lines were translated. """ languages: typing.List[CaptionTrack] = pydantic.Field() @@ -38,6 +43,11 @@ class TranscriptResponse(UniversalBaseModel): Every caption track the video offers. Empty when we did not list them on this call. """ + translation_languages: typing.List[str] = pydantic.Field() + """ + The codes language can name on this read. On captions, only the languages YouTube makes available for the video (its own tracks, and its auto-translate languages); on Premium, every language Arcmira translates into, at the Premium rate. + """ + lines: typing.Optional[typing.List[TranscriptResponseLinesItem]] = pydantic.Field(default=None) """ Present when timestamps is true. Cite start with watch_url. @@ -65,7 +75,7 @@ class TranscriptResponse(UniversalBaseModel): rows_billed: int = pydantic.Field() """ - Credits this captions read used, in rows (a row is 4 credits): 4 credits per started 15 minutes. 0 on a repeat of the same video, quality, language, and range inside the 7 day dedupe window. Premium reads report 0, because the Premium transcript job carries their charge. + Credits this captions read used, in rows (a row is 4 credits): 4 credits per started 15 minutes. 0 on a repeat of the same video, quality, language, translation, and range inside the 7 day dedupe window. Premium reads report 0, because the Premium transcript job carries their charge, except the read that pays for a Premium transcript in another language, which reports its rows. """ as_of: typing.Optional[str] = pydantic.Field(default=None) diff --git a/src/arcmira/types/transcript_result.py b/src/arcmira/types/transcript_result.py index 5e4c92c..17f068d 100644 --- a/src/arcmira/types/transcript_result.py +++ b/src/arcmira/types/transcript_result.py @@ -28,7 +28,9 @@ class TranscriptResult_Ready(UniversalBaseModel): quality: TranscriptResponseQuality source: TranscriptResponseSource language: str + base_language: str languages: typing.List[CaptionTrack] + translation_languages: typing.List[str] lines: typing.Optional[typing.List[TranscriptResponseLinesItem]] = None paragraphs: typing.Optional[typing.List[TranscriptResponseParagraphsItem]] = None speakers: typing.Optional[typing.List[TranscriptResponseSpeakersItem]] = None diff --git a/src/arcmira/types/transcript_search_chunk.py b/src/arcmira/types/transcript_search_chunk.py index b180c6d..7adba8b 100644 --- a/src/arcmira/types/transcript_search_chunk.py +++ b/src/arcmira/types/transcript_search_chunk.py @@ -5,6 +5,7 @@ import pydantic from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel from .named_entity_ref import NamedEntityRef +from .transcript_search_chunk_match import TranscriptSearchChunkMatch class TranscriptSearchChunk(UniversalBaseModel): @@ -65,17 +66,22 @@ class TranscriptSearchChunk(UniversalBaseModel): start_seconds: typing.Optional[int] = pydantic.Field(default=None) """ - Offset of the slice in the video, in seconds. + Where the slice starts in the video, in seconds. The line that matched q can be up to a minute later; line_start_seconds says when it is said. + """ + + line_start_seconds: typing.Optional[int] = pydantic.Field(default=None) + """ + When the line that best matches q starts, in whole seconds from the start of the video: the line where q starts as a phrase, otherwise the first of the fewest consecutive lines holding the most words of q. With by, the person's own line. watch_url and cite_line open here. Null when no line can be placed, for example on a passage indexed before line times were stored; watch_url and cite_line then open at start_seconds. """ watch_url: str = pydantic.Field() """ - Absolute watch URL on arcmira.com with the timestamp, e.g. https://arcmira.com/watch?v=...&t=4787. + Absolute watch URL on arcmira.com at line_start_seconds (start_seconds when it is null), e.g. https://arcmira.com/watch?v=...&t=4787. """ cite_line: typing.Optional[str] = pydantic.Field(default=None) """ - A ready citation line: title, clock, channel, date. + A ready citation line: title, clock, channel, date. The clock is the second watch_url opens at. """ score: float = pydantic.Field() @@ -83,6 +89,11 @@ class TranscriptSearchChunk(UniversalBaseModel): Retrieval score. Higher is a closer match. Not comparable across calls. """ + match: TranscriptSearchChunkMatch = pydantic.Field() + """ + How the passage holds q, ignoring case and punctuation: phrase holds every word of q in order, all_words holds each content word somewhere, partial holds only some. Passages holding the phrase or every word rank first. When no chunk is a phrase match, note says so. + """ + about: typing.Optional[typing.List[NamedEntityRef]] = pydantic.Field(default=None) """ Entities the passage is tagged about (excerpt pins, exact-name mentions, ad verdicts). Present on passages served from the spoken index. diff --git a/src/arcmira/types/transcript_search_chunk_match.py b/src/arcmira/types/transcript_search_chunk_match.py new file mode 100644 index 0000000..4af3052 --- /dev/null +++ b/src/arcmira/types/transcript_search_chunk_match.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptSearchChunkMatch = typing.Union[typing.Literal["phrase", "all_words", "partial"], typing.Any] diff --git a/tests/fixtures/readme-responses.json b/tests/fixtures/readme-responses.json index 6975508..5a5e828 100644 --- a/tests/fixtures/readme-responses.json +++ b/tests/fixtures/readme-responses.json @@ -42,7 +42,9 @@ "text": "TSMC on air", "start_seconds": 1, "watch_url": "https://arcmira.com/watch?v=dQw4w9WgXcQ&t=1", - "score": 1 + "score": 1, + "match": "phrase", + "line_start_seconds": 1 } ], "as_of": "x", @@ -82,6 +84,11 @@ "end": 4, "text": "Welcome back to the show." } + ], + "base_language": "x", + "translation_languages": [ + "en", + "es" ] }, "create_monitor": { diff --git a/tests/fixtures/transcription-responses.json b/tests/fixtures/transcription-responses.json index d04aab1..5ffd714 100644 --- a/tests/fixtures/transcription-responses.json +++ b/tests/fixtures/transcription-responses.json @@ -51,7 +51,12 @@ ], "rows_billed": 4, "as_of": "2026-09-01T09:00:00.000Z", - "note": "This transcript is the video's own caption track. `creator_captions` were written or approved by the channel; `third_party_quick` are YouTube's automatic captions and can misspell names and drop punctuation. `language` says which track you got." + "note": "This transcript is the video's own caption track. `creator_captions` were written or approved by the channel; `third_party_quick` are YouTube's automatic captions and can misspell names and drop punctuation. `language` says which track you got.", + "base_language": "en", + "translation_languages": [ + "en", + "es" + ] }, "responses": { "200": { @@ -106,7 +111,12 @@ ], "rows_billed": 4, "as_of": "2026-09-01T09:00:00.000Z", - "note": "This transcript is the video's own caption track. `creator_captions` were written or approved by the channel; `third_party_quick` are YouTube's automatic captions and can misspell names and drop punctuation. `language` says which track you got." + "note": "This transcript is the video's own caption track. `creator_captions` were written or approved by the channel; `third_party_quick` are YouTube's automatic captions and can misspell names and drop punctuation. `language` says which track you got.", + "base_language": "en", + "translation_languages": [ + "en", + "es" + ] } }, "202": { @@ -272,7 +282,12 @@ "revision": "rev_1", "rows_billed": 0, "as_of": "2026-08-05T00:00:00.000Z", - "note": "Premium transcripts are Arcmira's own. Every Premium transcript is diarized: each line carries a speaker. We identify each speaker from the audio, the video, and internal and community review. We are right most of the time and wrong sometimes, so when a name matters, say it came from Arcmira's speaker identification and cite the line." + "note": "Premium transcripts are Arcmira's own. Every Premium transcript is diarized: each line carries a speaker. We identify each speaker from the audio, the video, and internal and community review. We are right most of the time and wrong sometimes, so when a name matters, say it came from Arcmira's speaker identification and cite the line.", + "base_language": "en", + "translation_languages": [ + "en", + "es" + ] } }, "job_ready": { diff --git a/tests/test_readme.py b/tests/test_readme.py index dceeb96..2268ccf 100644 --- a/tests/test_readme.py +++ b/tests/test_readme.py @@ -51,6 +51,19 @@ def respond(request): self.assertEqual(json.loads(requests[3].content), dict(names=[dict(name='PlanetScale', type='organization')])) self.assertTrue(all(r.headers['Authorization'] == 'Bearer fixture-key' for r in requests)) + def test_search_exposes_match_and_nullable_line_timestamp(self): + for seconds in (1, None): + with self.subTest(seconds=seconds): + body = json.loads(json.dumps(RESPONSES['search'])) + body['chunks'][0]['line_start_seconds'] = seconds + with httpx.Client(transport=httpx.MockTransport( + lambda request: httpx.Response(200, json=body) + )) as http: + client = Arcmira(api_key='fixture-key', httpx_client=http, max_retries=0) + hit = client.transcripts.search(q='GPU demand', limit=20).chunks[0] + self.assertEqual(hit.match, 'phrase') + self.assertEqual(hit.line_start_seconds, seconds) + def test_supplier_comparison_resolves_speaker_and_searches_both_windows(self): requests = [] def respond(request): diff --git a/tests/test_transcription.py b/tests/test_transcription.py index fdfdb1e..3e80f59 100644 --- a/tests/test_transcription.py +++ b/tests/test_transcription.py @@ -141,10 +141,14 @@ def test_ready_read(self): self.assertEqual(ready.status_code, 200) self.assertIsInstance(ready.data, TranscriptResult_Ready) self.assertEqual(ready.data.quality, 'captions') + self.assertEqual(ready.data.base_language, 'en') + self.assertEqual(ready.data.translation_languages, ['en', 'es']) self.assertEqual(ready.data.lines[0].text, FIXTURES['get_transcript']['body']['lines'][0]['text']) premium = self.client.transcripts.get('dQw4w9WgXcQ', quality='premium') self.assertIsInstance(premium, TranscriptResult_Ready) self.assertEqual(premium.quality, 'premium') + self.assertEqual(premium.base_language, 'en') + self.assertEqual(premium.translation_languages, ['en', 'es']) self.assertEqual(premium.revision, FIXTURES['premium_ready']['body']['revision']) def test_premium_pending_carries_the_job_and_retry_after(self):