Skip to main content

TVPulse Python SDK

日本のテレビ放送に関する検索・集計APIを、型付きのPythonモデルで利用するためのSDKです。カスタマーAPIの説明、入力検証、エラー情報、有限リトライをSDKの公開契約として扱います。

インストール

pip install tvpulse==1.0.0

対応するPythonは3.10、3.11、3.12です。APIキーはコードに直接書かず、TVPULSE_API_KEYで渡してください。

export TVPULSE_API_KEY='組織のAPIキー'

カスタマーAPIの公式ドキュメントは、APIドキュメントをご覧ください。

0.xからの移行 (Migrating from 0.x)

PyPIのtvpulse 0.0.0-0.2.0は、本SDKとは別の、アーカイブ済みリポジトリ (infinimind-inc/tvpulse_python_sdk)由来の旧SDKです。検索(searches)・エクスポート(exports) APIを含む現行のカスタマーAPIには対応していません。1.0.0はその後継ではなく、 現行のTVPulseカスタマーAPI向けに書き直した別物のSDKです。

  • 旧SDKを使い続ける場合は pip install "tvpulse<1" でピン留めしてください。
  • 旧SDKのモジュール構成は本リポジトリの外にあり、対応関係を機械的に検証できないため 網羅的な対応表は提供しません。確認できている範囲では、旧SDKのクライアントに相当するのが 本SDKのTVPulseClient/AsyncTVPulseClient(下記「同期クライアント」参照)です。
  • 移行時の疑問は support@infinimind.io までご連絡ください。

同期クライアント

from tvpulse import SearchTrendRequest, TVPulseClient

request = SearchTrendRequest(
    keyword="チーズケーキ",
    start_date="2026-05-01 00:00:00",
    end_date="2026-05-01 23:59:59",
    streams=["cx", "ntv"],
    frequency="hour",
)

with TVPulseClient() as client:
    response = client.combined.search(request)
    print(response.data)

原文検索のキーワードなしモード

SearchRawRequestのkeywordは省略できます。キーワードなしの場合は、streamsとstart_date/end_dateの半開区間(最大24時間)か、正のairing_idのどちらか一つを指定します。キーワードを指定する場合は、streamsと開始・終了日時も必要です。modeはキーワード検索だけで使えます。

cm_filterはinclude(既定)、exclude、onlyから選べます。limitは1〜100件で、応答のnext_page_tokenを次のリクエストに渡すと続きのページを取得できます。検索コンテキストは約2分で失効するため、cursor_expiredの場合は最初のページから再実行してください。

キーワードなし検索の局コードは1〜15件の重複しないサポート対象局です。15局の入力は受け付けますが、標準料金外のためGatewayでcustom_pricing_required(403)になります。14局以下は既存の標準料金で計算されます。

from tvpulse import SearchRawRequest, TVPulseClient

request = SearchRawRequest(
    start_date="2026-08-01 00:00:00",
    end_date="2026-08-01 01:00:00",
    streams=["ntv", "tbs"],
    cm_filter="only",
    limit=25,
)

with TVPulseClient() as client:
    page = client.text.search_raw(request)
    if page.next_page_token:
        next_page = client.text.search_raw(request.model_copy(update={"next_page_token": page.next_page_token}))

検索定義と有界投影

新しい/v1/api/searchesは、検索条件を軽量なオーナースコープの定義として保存し、必要な投影だけを個別に取得します。作成時は、同じオーナー内で再送を識別する不透明なidempotency_keyを必ず指定してください。

from datetime import datetime, timezone

from tvpulse import SearchDefinitionCreateRequest, TVPulseClient

request = SearchDefinitionCreateRequest(
    keyword="コーヒー",
    visual_query="コーヒーのパッケージ",
    sources=["ocr", "asr", "visual"],
    streams=["cx", "ntv"],
    start_at=datetime(2026, 5, 1, tzinfo=timezone.utc),
    end_at=datetime(2026, 5, 2, tzinfo=timezone.utc),
)

with TVPulseClient() as client:
    search = client.searches.create(request, idempotency_key="customer-search-20260501-1")
    trend = search.trend(resolution="hour")
    breakdown = search.breakdown()
    evidence_page = search.evidence(limit=50)
    context = search.context(max_chars=5_000)
    programs = search.programs()

projection_hints.evidence_limitは定義作成時に宣言する任意の深度で、既定値は50、範囲は1〜100です。定義作成の料金はこの宣言を使います。search.evidence(limit=...)のGETはクレジットを消費せず、要求値が宣言値を超える場合はHTTP 200のまま宣言値までのページを返します。ページの継続では最初の要求と同じlimitを使ってください。これは、定義作成後にGETのlimitだけを増やして追加の深度を取得できない、意図的な契約変更です。

search.evidence()は1ページだけを返します。evidence_page.next_cursorがある場合は、その値を次の呼び出しのcursorに渡し、処理済みページを保持しないでください。サーバーの要求上限は1ページ100件、カーソル256文字です。投影が個別に非同期として受理された場合はHTTP 202とProjectionStatusResponseが返ります。定義が期限切れの場合は安全なTVPulseSearchExpiredError(HTTP 410)が返ります。

Visual ANNはランキングされたvisual_evidenceであり、露出母数、シェア、露出秒数ではありません。検証済みの時間区間をcoalesceしてunionする将来のexposure productだけが、露出秒数を提供できます。

レコード・集計・エクスポート(アルファ版)

/v1alpha1/のレコード関連APIはアルファ版です。契約はリリース間で変わる可能性があります。キーワードなしで、OCR観測レコード(client.text)とASRセグメントレコード(client.voice)をカーソルページングで列挙できます。

  • 応答のタイムスタンプはすべてRFC3339の+09:00(JST)付きです。入力は任意のオフセット付きRFC3339を受け付けます。期間は半開区間[start_at, end_at)です。
  • 1ページが課金単位です。iter_records()はnext_cursorを自動で追跡し、max_recordsで上限を指定できます。カーソルが進まなくなった場合(ライブ端)も安全に停止します。
  • すべての応答にcoverage(ストリームごとのhigh_water_markとarchive_floor、番組フィルタ時は解決済み区間)が含まれます。
  • commercial_statusはcommercial | program | unknownの3値です。CM区間メタデータから判定され、レコード側のフラグは使いません。
from tvpulse import TVPulseClient

with TVPulseClient() as client:
    for record in client.text.iter_records(
        streams=["ntv"],
        start_at="2026-08-13T00:00:00+09:00",
        end_at="2026-08-14T00:00:00+09:00",
        page_size=1000,
        max_records=5000,
    ):
        print(record.observed_at, record.commercial_status, record.text)

    aggregates = client.text.aggregates(
        streams=["ntv"],
        start_at="2026-08-13T00:00:00+09:00",
        end_at="2026-08-14T00:00:00+09:00",
        granularity="hour",
        group_by="stream",
    )

集計(aggregates())はgranularity(minute | hour | day | week)とgroup_by(stream | total)を受け付けます。単位はOCR観測数またはASRセグメント数で、モダリティをまたぐ合算はありません。アルファ版ではcommercialはincludeのみサーバーが受理します。

大きな期間はclient.exportsで非同期エクスポートします。dry_run=Trueの見積りが提出時の価格になり、課金は提出時に発生します。状態ポーリングも通常リクエストとして課金されるため、wait()の既定poll_intervalは5秒です。

from tvpulse import ExportCreateRequest, TVPulseClient

request = ExportCreateRequest(
    modality="ocr",
    streams=["ntv"],
    start_at="2026-08-13T00:00:00+09:00",
    end_at="2026-08-14T00:00:00+09:00",
    idempotency_key="export-20260813-ntv-1",
)

with TVPulseClient() as client:
    estimate = client.exports.create(request.model_copy(update={"dry_run": True}))
    accepted = client.exports.create(request)
    final = client.exports.wait(accepted.export_id, poll_interval=5, timeout=1800)
    paths = client.exports.download(accepted.export_id, "./exports", verify_checksums=True)

download()はマニフェストの各パーツ(presigned URL、15分で失効、ポーリングごとに再発行)をディスクへストリーミングし、sha256を検証してからファイルパスの一覧を返します。チェックサム不一致はTVPulseExportIntegrityErrorになります。エクスポートの成果物は7日で失効します。

トレンドカードとウォッチ(アルファ版)

client.trend_cardsは、Xまたはニュースをソースにした自由なトピックの分析カードを生成します。create()は、生成を受理したTrendCardAcceptedResponseまたは、十分に新しいカードを再利用したTrendCardJobResponseを返します。create_and_wait()は受理されたカードだけをポーリングし、キャッシュ済みのHTTP 200は追加の状態確認なしで返します。

client.watchesは、保存したカード条件を読み取り時だけ更新するlazy watchを管理します。latest()の200応答は、新鮮なカード、refreshing=Trueの前回カード、またはcard=Noneとrefresh_deferred_concurrency_limit警告を持つ遅延応答です。初回生成はWatchLatestAcceptedResponseの202です。200応答のwarningsは常に確認してください。

カードの状態確認は通常のリクエストとして課金されます。create_and_wait()のpoll_intervalには余裕を持たせてください。404はTVPulseNotFoundError、409はTVPulseConflictErrorに変換されます。

from tvpulse import TrendCardCreateRequest, TVPulseClient, WatchCreateRequest

with TVPulseClient() as client:
    card = client.trend_cards.create_and_wait(
        TrendCardCreateRequest(topic="コーヒー", source_type="x", window="weekly"),
        poll_interval=5,
    )
    watch = client.watches.create(
        WatchCreateRequest(topic="コーヒー", source_type="x", window="weekly"),
    )
    latest = client.watches.latest(watch.watch_id)
    print(card.status, latest)

非同期クライアント

import asyncio

from tvpulse import AsyncTVPulseClient, SearchRawRequest

async def main():
    request = SearchRawRequest(
        keyword="ニュース",
        start_date="2026-05-01 00:00:00",
        end_date="2026-05-01 00:30:00",
        streams=["ntv"],
    )

    async with AsyncTVPulseClient() as client:
        response = await client.voice.search_raw(request)
        for hit in response.data:
            print(hit.text)


asyncio.run(main())

公開操作の対応表

Gateway操作 SDKの呼び出し 応答モデル
POST /v1/api/combined/search client.combined.search() CombinedSearchResponse
POST /v1/api/combined/search/stream client.combined.search()が条件に応じて自動選択 CombinedSearchResponse
POST /v1/api/combined/summary client.combined.summary() SummaryResponse
POST /v1/api/combined/nlp client.combined.nlp() CombinedNLPResponse
POST /v1/api/combined/related-keywords client.combined.related_keywords() RelatedKeywordsResponse
POST /v1/api/text/search/raw client.text.search_raw() TextSearchResponse
POST /v1/api/voice/search/raw client.voice.search_raw() VoiceSearchResponse
POST /v1/api/searches client.searches.create() product-shaped search handle
GET /v1/api/searches/{search_id} client.searches.get() product-shaped search handle
GET /v1/api/searches/{search_id}/trend search.trend() TrendProjectionResponseまたはProjectionStatusResponse
GET /v1/api/searches/{search_id}/breakdown search.breakdown() BreakdownProjectionResponseまたはProjectionStatusResponse
GET /v1/api/searches/{search_id}/evidence search.evidence() EvidenceProjectionResponseまたはProjectionStatusResponse
GET /v1/api/searches/{search_id}/context search.context() ContextProjectionResponseまたはProjectionStatusResponse
GET /v1/api/searches/{search_id}/programs search.programs() ProgramsProjectionResponseまたはProjectionStatusResponse
GET /v1alpha1/trend-watch/catalog/reports client.trend_watch.list_reports() TrendWatchReportListResponse
GET /v1alpha1/trend-watch/catalog/reports/{report_id} client.trend_watch.get_report() TrendWatchReportDetail
POST /v1alpha1/trend-watch/catalog/reports/exports client.trend_watch.export_reports() TrendWatchExport
POST /v1alpha1/visual-search/searches client.visual.search() VisualSearchResponseまたはVisualSearchAcceptedResponse
POST /v1/api/credits/estimate client.credits.estimate() CreditEstimate
POST /v1/api/credits/estimate/rows client.credits.estimate_rows() CreditEstimateRows
GET /v1/api/credits client.credits.balance() CreditBalance
GET /v1alpha1/text/records(アルファ版) client.text.records() / client.text.iter_records() TextRecordsPage / TextRecordのイテレータ
GET /v1alpha1/voice/records(アルファ版) client.voice.records() / client.voice.iter_records() VoiceRecordsPage / VoiceRecordのイテレータ
GET /v1alpha1/text/aggregates(アルファ版) client.text.aggregates() RecordsAggregatesResponse
GET /v1alpha1/voice/aggregates(アルファ版) client.voice.aggregates() RecordsAggregatesResponse
POST /v1alpha1/exports(アルファ版) client.exports.create() ExportAcceptedResponseまたはExportEstimate
GET /v1alpha1/exports(アルファ版) client.exports.list() ExportListResponse
GET /v1alpha1/exports/{export_id}(アルファ版) client.exports.get() / client.exports.wait() / client.exports.download() ExportStatusResponse / ダウンロード済みファイルパス
POST /v1alpha1/trend-cards(アルファ版) client.trend_cards.create() / client.trend_cards.create_and_wait() TrendCardAcceptedResponseまたはTrendCardJobResponse
GET /v1alpha1/trend-cards/{card_id}(アルファ版) client.trend_cards.get() TrendCardJobResponse
POST /v1alpha1/watches(アルファ版) client.watches.create() WatchResponse
GET /v1alpha1/watches(アルファ版) client.watches.list() WatchListResponse
GET /v1alpha1/watches/{watch_id}/latest(アルファ版) client.watches.latest() WatchLatestResponseまたはWatchLatestAcceptedResponse
DELETE /v1alpha1/watches/{watch_id}(アルファ版) client.watches.delete() None

Published search projections are synchronous. search.projection_status() remains compatibility-only until a real producer exists; no supported production flow creates a projection ID, and the status route is not published in customer OpenAPI.

ビジュアル検索が受付応答になった場合は、公開操作数に含まれない状態確認用のclient.visual.get_search()を使えます。受付から完了までをまとめる場合はclient.visual.search_and_wait()を使ってください。状態確認のパスは現在のゲートウェイ実装にありますが、公開OpenAPIでは非表示です。

クレジットの見積もりと残高

実行前に消費クレジットを確認し、残高と突き合わせられます。どちらの呼び出しも0クレジットです(レート制限は消費します)。残高が0のキーやAPIサブスクリプションを持たないキーでも呼び出せます。

from tvpulse import SearchTrendRequest, TVPulseClient

request = SearchTrendRequest(
    keyword="コーヒー",
    start_date="2026-05-01 00:00:00",
    end_date="2026-05-02 00:00:00",
    streams=["ntv"],
)

with TVPulseClient() as client:
    est = client.credits.estimate("/v1/api/combined/search", request)
    if est.balance.sufficient:
        response = client.combined.search(request)
    bal = client.credits.balance()
    print(est.credits, est.outcome, est.factor_revision, bal.total_credits)

見積もりは実行時とまったく同じ計算式で計算されるため、同一のリクエストボディ・同一の料金係数リビジョンなら実際の請求と一致します。応答のfactor_revisionが、どの料金表で計算されたかの証明です。outcomeが"enterprise_review"の場合、そのワークロードはEnterprise/個別見積もりの対象です。実行時には403(custom_pricing_required)で拒否されクレジットは消費されないため、creditsは0で、flagsにcustom_pricing_required:*(対象の条件)が入ります。

estimate_rows: 検索結果行数の見積もり

client.credits.estimate_rows()はPOST /v1/api/credits/estimate/rowsを呼び出し、検索を実行せずに完全一致する行数と取得クレジットを返します。こちらも0クレジットのプリフライトです。エンドポイントは/v1/api/searchesのみを受け付けます。

from datetime import datetime, timezone

from tvpulse import SearchDefinitionCreateRequest, TVPulseClient

request = SearchDefinitionCreateRequest(
    keyword="コーヒー",
    sources=["ocr", "asr", "visual"],
    streams=["cx", "ntv"],
    start_at=datetime(2026, 5, 1, tzinfo=timezone.utc),
    end_at=datetime(2026, 5, 2, tzinfo=timezone.utc),
)

with TVPulseClient() as client:
    rows = client.credits.estimate_rows("/v1/api/searches", request)
    print(rows.total_rows, rows.retrieval, rows.outcome)

total_rowsはOCR/ASRのテキストソースに一致する行数の合計です。ビジュアル行は行数に含まれず、visual_excludedがtrueでflagsにvisual_rows_not_countedが入ります。retrievalは完全な結果セットを取得するためのクレジットで、フラットな1クレジットの継続ページを含みます。outcomeが"enterprise_review"の場合、宣言されたリクエストまたは取得のページング上限を超えるワークロードで、Enterprise/個別見積もりの対象です。declared_request_creditsは宣言されたリクエスト自体のクレジットです。

残高応答のbucketsは種別ごとの内訳です。プラン付帯の月次クレジット(allowance)は毎月リセットされ繰り越されません。購入済みクレジットパック(pack)は失効しないためexpires_atはnullです。

入力と応答

日時は操作ごとの契約に合わせて検証します。通常の検索は日本時間のタイムゾーンなしYYYY-MM-DD HH:mm:ss、ビジュアル検索はオフセット付きRFC3339です。大文字の検索モードANDとORは互換入力として受け付けますが、送信値はandとorに正規化します。

辞書を渡す場合も、型付きリクエストモデルを通した場合と同じ検証を行います。応答は型付きモデルで返り、未知の応答フィールドも保持します。辞書が必要な連携では、各応答のto_raw()を使ってください。

エラーとリトライ

次の例外を公開しています。

  • TVPulseValidationError: 400または422
  • TVPulseAuthError: 401
  • TVPulseAuthorizationError: 権限不足の403
  • TVPulseNotFoundError: 404(例: export_not_found。互換性のためTVPulseUnknownServerErrorのサブクラス)
  • TVPulseConflictError: 409(例: エクスポートの同時実行上限。互換性のためTVPulseUnknownServerErrorのサブクラス)
  • TVPulseSearchExpiredError: 期限切れの検索定義(410、error_code=search_expired)
  • TVPulseInsufficientCreditError: error_code=insufficient_creditの403
  • TVPulseRateLimitError: 429
  • TVPulseBackendError: 5xx
  • TVPulseUnknownServerError: 契約外のステータスまたは解釈できない応答
  • TVPulseTimeoutError: タイムアウト
  • TVPulseNetworkError: 接続などのネットワークエラー
  • TVPulseExportIntegrityError: エクスポートパーツのsha256チェックサム不一致

APIエラーにはstatus_code、error_code、correlation_id、retry_after、retryable、bodyが含まれます。旧SDKとの互換性のためcodeとrequest_idも利用できます。

既定では最大2回の追加試行を行います。ネットワークエラー、429、5xxだけを対象にし、429と5xxではRetry-Afterを優先します。それ以外の4xxは再試行しません。必要に応じてmax_retries=0、retry_backoff=秒数で調整できます。

from tvpulse import TVPulseClient

with TVPulseClient(max_retries=2, retry_backoff=0.5) as client:
    response = client.text.search_raw(request)

サンプル

サンプルの日付は契約形式を示す固定例です。利用時は、利用可能な放送期間に置き換えてください。

Release files for tvpulse 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tvpulse 1.0.0
File Size Uploaded
tvpulse-1.0.0.tar.gz 39.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tvpulse 1.0.0
File Interpreter ABI Platform
tvpulse-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 79.7 kB

Release files / tvpulse-1.0.0.tar.gz

Download URL tvpulse-1.0.0.tar.gz
Size 39.1 kB
Tags Source
SHA-256 checksum
How to use checksums
401d590284f73ad9b5b5ac8df43b3c153ad2868ed326ffcf487af39d65cf9eae
BLAKE2b-256 checksum
How to use checksums
a94dbe07dab3e1d6652f58c87753a65a3da701c3f4b3ab089fec5875e077260d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / tvpulse-1.0.0-py3-none-any.whl

Download URL tvpulse-1.0.0-py3-none-any.whl
Size 40.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f8964df82a73011b88b11afc27d50c438fd38f493e3b23512c5bc42f51b828eb
BLAKE2b-256 checksum
How to use checksums
882949e7b58a1ecca2c094f042003d620a700b5788d65313dbab03d737658e1f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

0.2.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page