Skip to main content

Official Python SDK for the BrightBean API — YouTube packaging scoring.

Project description

brightbean — Python SDK

Official Python SDK for the BrightBean API. YouTube creator analytics: score titles and thumbnails for click-through-rate, analyze video hooks, benchmark channels and videos against their niche, and surface ranked content opportunities.

Install

pip install brightbean

Requires Python 3.10+.

Quickstart

from brightbean import BrightBean

with BrightBean(api_key="bb_...") as client:
    result = client.score(
        title="10 Tips to Boost Your Coding Productivity",
        thumbnail_url="https://i.ytimg.com/vi/abc123/maxresdefault.jpg",
    )
    print(f"score={result.score:.2f} percentile={result.percentile}")

Async

import asyncio
from brightbean import AsyncBrightBean

async def main():
    async with AsyncBrightBean(api_key="bb_...") as client:
        return await client.score(title="My epic video")

asyncio.run(main())

Every method documented below has the same signature on AsyncBrightBean; just await the call.


API reference

Response objects are typed dataclasses. Field names match the wire format (snake_case). Nullable fields are explicitly annotated … | None.

client.score(...) — packaging CTR

Score a YouTube packaging (title and/or thumbnail) for predicted click-through rate. The server auto-detects the mode from your inputs and charges credits accordingly.

result = client.score(
    title="10 Tips to Boost Your Coding Productivity",
    thumbnail_url="https://i.ytimg.com/vi/abc123/maxresdefault.jpg",
)
print(result.score, result.percentile, result.niche_label)

Parameters

Name Type Required Default Description
title str | None conditional* None Max 250 chars.
thumbnail_url str | None conditional* None Public URL. Max 1024 chars. Mutually exclusive with thumbnail_base64.
thumbnail_base64 str | None conditional* None Base64-encoded image. Max 12.5 MB encoded. Mutually exclusive with thumbnail_url.
channel_url str | None no None Reserved — currently accepted but not consumed by the model.

* At least one of title, thumbnail_url, or thumbnail_base64 is required.

Returns PackagingScoreResponse

Field Type Description
score_id UUID Opaque identifier for this scoring call.
score float (0–1) Calibrated CTR percentile rank.
percentile int (0–100) round(score * 100).
raw_score float | None Continuous pre-calibration sigmoid output. Useful for fine-grained comparisons that the percentile-ranked score would round into the same plateau.
mode "title" | "thumbnail" | "combined" Server-detected scoring mode.
niche_slug str Canonical niche slug. Same taxonomy as /v1/research/* and /v1/benchmark/*.
niche_label str Human-readable niche name.
niche_confidence float Confidence (cosine similarity) of the niche assignment.

Cost: 1 credit for title-only, 2 credits for thumbnail-only, 3 credits for combined (the combined mode captures interaction effects the single-input modes miss).


client.video_hook(...) — hook quality of the first ~6 s

Score the first ~6 seconds of a YouTube video for hook quality. Returns a structured analysis with archetype classification, five dimension scores, and actionable strengths / weaknesses / suggestions.

result = client.video_hook(
    youtube_url="https://www.youtube.com/watch?v=dQw4w9WgXcQ",
)
print(result.primary_archetype, result.overall_score)
print(result.scores.clarity, result.scores.tension)

Parameters

Name Type Required Default Description
youtube_url str yes Public YouTube URL, max 1024 chars. Accepts youtube.com/watch?v=…, youtu.be/…, youtube.com/shorts/…, youtube.com/embed/….

Returns VideoHookScoreResponse

Field Type Description
score_id UUID Opaque identifier for this scoring call.
primary_archetype str One of 13 archetypes (curiosity_gap, direct_question, bold_claim, in_medias_res, stakes_statement, promise, pattern_interrupt, authority, demonstration, empathy, negative_framing, cold_open, greetings).
secondary_archetype str | None Optional second archetype if the hook blends two.
scores Scores (nested) Five 0–10 dimension scores. See below.
overall_score int (0–100) Holistic hook-quality score.
transcript str First-6-seconds transcript. May be blank for music-only or visual-only openings.
visual_summary str Short description of the visual content.
strengths list[str] (2–4 items) What the hook does well.
weaknesses list[str] (2–4 items) What the hook does poorly.
suggestions list[str] (2–4 items) Concrete edits the creator could make.
delta_vs_niche_top int Difference between this hook's overall_score and the niche's top performers.
key_differences_vs_top list[str] (1–3 items) Specific ways this hook differs from niche-top hooks.

Nested scores:

Field Type Description
clarity int (0–10) How quickly the viewer understands the topic.
specificity int (0–10) How concrete the promise is.
tension int (0–10) Stakes / curiosity gap.
visual_energy int (0–10) Movement, cuts, visual contrast.
pace int (0–10) Words / cuts per second.

Cost: 10 credits per call.


client.benchmark_channel(...) — channel vs. niche

Benchmark a YouTube channel against its niche distribution. Fetches the channel's recent videos, classifies the niche, and percentile-ranks engagement and title patterns.

result = client.benchmark_channel(
    url="https://www.youtube.com/@MarquesBrownlee",
)
print(result.niche.slug, result.niche.match_strength)
print(result.channel.engagement_percentiles.overall)

Parameters

Name Type Required Default Description
url str yes Channel URL or @handle, max 1024 chars. Accepts youtube.com/@handle, /channel/UC…, /c/<name>, /user/<legacy>.

Returns BenchmarkChannelResponse — two top-level objects, channel and niche.

channel

Field Type Description
channel_id str YouTube channel ID (UC…).
title str Channel display name.
subscriber_count int Public subscriber count at fetch time.
video_count int Total public videos.
engagement object Raw ratios. view_to_sub_ratio, like_to_view_ratio, comment_to_view_ratio — each float | None.
engagement_percentiles object The same three ratios plus overall, each ranked into int (0–100) | None against the niche distribution.
sample_window_days int Date span (in days) of the fetched sample, so callers can weight the signal against the channel's posting cadence.
title_patterns object Aggregate stats: mean_length_chars, mean_length_words, share_with_question_mark, share_with_number, median_uppercase_ratio, share_with_emoji. Each float | None (or int | None for the length fields).

niche

Field Type Description
slug str Canonical niche slug.
name str Human-readable niche name.
match_score float Cosine similarity of the channel to the niche centroid.
match_strength "strong" | "moderate" | "weak" Categorized confidence.
engagement object Three ratio-keyed distribution objects (view_to_sub_ratio, like_to_view_ratio, comment_to_view_ratio). Each carries p10, p25, p50, p75, p90, p95float | None.
title_patterns object Same fields as channel.title_patterns plus common_niche_phrases: list of {phrase: str, frequency: float | None, used_by_channel: bool}.
exemplar_channels list (up to 5) {title: str | None, subscriber_count: int | None}.

Cost: 5 credits per call.


client.benchmark_video(...) — single video vs. niche

Benchmark one YouTube video against its niche distribution.

result = client.benchmark_video(
    url="https://www.youtube.com/watch?v=dQw4w9WgXcQ",
)
print(result.video.engagement_percentiles.overall)
print(result.video.title_patterns.fits_niche_patterns)

Parameters

Name Type Required Default Description
url str yes YouTube video URL, max 1024 chars. Accepts youtube.com/watch?v=…, youtu.be/…, youtube.com/shorts/…, youtube.com/embed/….

Returns BenchmarkVideoResponse — two top-level objects, video and niche.

video

Field Type Description
video_id str 11-character YouTube video ID.
title str Video title. May be blank.
channel_title str Owning channel's display name. May be blank.
published_at datetime UTC publish timestamp.
view_count int Public view count at fetch time.
like_count int | None None when likes are hidden on the video.
comment_count int | None None when comments are disabled.
engagement object view_to_sub_ratio, like_to_view_ratio, comment_to_view_ratio — each float | None.
engagement_percentiles object Same three ratios plus overall, each int (0–100) | None.
title_patterns object length_chars (int | None), has_question (bool), has_number (bool), has_emoji (bool), fits_niche_patterns (bool).

niche

Same shape as benchmark_channel's nicheslug, name, match_score, match_strength, ratio-keyed engagement distributions, title_patterns with common_niche_phrases. The used_by_channel flag on each common phrase here means "did this single video's title use the phrase"; the serializer field name stays the same across both benchmark endpoints.

Cost: 3 credits per call.


client.niches() — list catalogued niches

List every catalogued YouTube niche for the active research run. Use the slug values as the niche argument to client.content_gaps(...).

result = client.niches()
for n in result.niches:
    print(n.slug, n.gap_count)

Parameters: none.

Returns NicheListResponse

Field Type Description
niches list[NicheSummary] Flat array of catalogued niches.

Each NicheSummary:

Field Type Description
slug str Canonical niche slug.
name str Human-readable name.
gap_count int Number of content gaps catalogued for that niche in the active run, for the default gap types. 0 means the niche exists in the taxonomy but no gaps have been ranked yet.

Cost: 1 credit per call.


client.content_gaps(...) — ranked content opportunities

Return ranked content opportunities (gaps) for a YouTube niche.

result = client.content_gaps(
    niche="python_programming_tutorials",
    gap_type=["underserved", "stale"],   # or just "underserved"
    limit=10,
    min_score=60,
)
for gap in result.gaps:
    print(gap.opportunity_score, gap.canonical_title)

Parameters

Name Type Required Default Description
niche str yes Slug from client.niches(), max 100 chars.
gap_type str | list[str] | None no None (server defaults to ["underserved", "stale"]) One of "underserved", "stale", "competitive", or a list of those. "competitive" is opt-in (omit from the list to exclude it).
limit int | None no None (server default: 20) 1–50.
min_score int | None no None (server default: 0) Minimum opportunity_score (0–100) to include.

Returns ContentGapsResponse

Field Type Description
niche object {slug: str, name: str}.
gaps list[ContentGapsItem] Ranked gaps (see below).

Each gap (ContentGapsItem):

Field Type Description
canonical_title str Suggested canonical title for the gap.
opportunity_score int (0–100) Composite ranking score.
gap_type "underserved" | "stale" | "competitive" Which category the gap falls into.
components object {demand: float, supply: float, recency: float} — the three signals that make up opportunity_score.
explanation str Short rationale for why this gap exists. May be blank.
suggested_angles list[str] Concrete video angles the creator could take.
evidence object Supporting signals. See below.

Nested evidence:

Field Type Description
newest_quality_video_age_days int | None Age (in days) of the newest high-quality video in this gap; None if no qualifying video exists.
trends_appearance_count int How many times this topic appeared in trends signals.
autocomplete_rank int | None YouTube autocomplete rank, if observed.
residual_outlier_count int Number of outlier signals contributing to the score.
top_competitors list (up to 5) {title: str, channel_title: str, subscriber_count: int | None, view_count: int, age_days: int, published_at: datetime}.
related_queries list[str] (up to 5) Adjacent search queries.

Cost: 5 credits per call.


client.me() — account info

Return account info and plan details for the authenticated API key. Free to call.

me = client.me()
print(me.email, me.plan.name, me.credits_remaining)

Parameters: none.

Returns MeResponse

Field Type Description
email str Account email.
full_name str May be blank if the user hasn't set it.
plan Plan (nested) {name: str, slug: str, monthly_credits: int, rate_limit_per_minute: int}.
credits_remaining int Credits left in the current billing period.
period_end datetime When the current billing period ends and credits reset.

Cost: free (no credits deducted).


client.usage() — credit balance + recent activity

Return the credit balance and recent API activity for the authenticated API key. Free to call.

usage = client.usage()
print(usage.credits_remaining, "/", usage.credits_total)
for entry in usage.recent_activity:
    print(entry.created_at, entry.endpoint, entry.delta)

Parameters: none.

Returns UsageResponse

Field Type Description
credits_remaining int Credits left in the current billing period.
credits_total int Total credits for the current plan (resets at period_end).
period_end datetime When the current billing period ends.
recent_activity list[CreditLedgerEntry] Up to 20 most-recent credit-ledger entries.

Each CreditLedgerEntry:

Field Type Description
delta int Signed change. Negative for API charges.
balance_after int Credit balance immediately after this entry.
reason str Free-form reason (e.g. "api_call", "monthly_reset").
endpoint str API endpoint path that triggered the entry. May be blank for non-API entries.
created_at datetime UTC timestamp.

Cost: free (no credits deducted).


Errors

The SDK raises typed exceptions for each documented status code:

Status Exception Notes
400 BadRequestError Validation failed.
401 AuthenticationError Missing, invalid, or expired API key.
402 InsufficientCreditsError Account is out of credits.
429 RateLimitError Too many requests. retry_after (int seconds).
503 ServiceUnavailableError Scoring service down. retry_after (int seconds).
5xx ServerError Other server-side failure.
network NetworkError DNS, connection refused, TLS, timeout.

All API errors inherit BrightBeanAPIError. Network failures raise NetworkError. Both inherit BrightBeanError for catch-all handling.

Retries

Idempotent GETs (me(), usage()) retry automatically on 5xx and network errors with exponential backoff + jitter. Tune via max_retries:

client = BrightBean(api_key="bb_...", max_retries=5)

POST endpoints and paid GETs are never auto-retried because the server may have charged credits before the network response was lost. Catch RateLimitError or ServiceUnavailableError and back off using retry_after if you need retry semantics.

Configuration

BrightBean(
    api_key="bb_...",                   # required
    base_url="https://api.brightbean.xyz",
    timeout=30.0,                       # seconds, or httpx.Timeout(...)
    max_retries=3,                      # idempotent GETs only
    user_agent=None,                    # override the default UA string
)

base_url defaults to https://api.brightbean.xyz. Override for staging or local testing.

Development

pip install -e .[dev]
openapi-python-client generate \
    --path ../../openapi.yaml \
    --config generator.yaml \
    --meta=none \
    --output-path ./brightbean/_generated \
    --overwrite
pytest && ruff check . && mypy brightbean

The brightbean/_generated/ directory is regenerated from the upstream OpenAPI spec. The hand-written facade (brightbean/_client.py, brightbean/errors.py, brightbean/__init__.py) wraps it to give the ergonomic BrightBean import shape.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

brightbean-1.0.0rc4.tar.gz (38.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

brightbean-1.0.0rc4-py3-none-any.whl (80.7 kB view details)

Uploaded Python 3

File details

Details for the file brightbean-1.0.0rc4.tar.gz.

File metadata

  • Download URL: brightbean-1.0.0rc4.tar.gz
  • Upload date:
  • Size: 38.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for brightbean-1.0.0rc4.tar.gz
Algorithm Hash digest
SHA256 6ce5c6bbc413eb964c7e54f2ff10662af12469e8654bb38ca772d20fdbd3558d
MD5 eca38d74234a0b93f11a69232bbe226a
BLAKE2b-256 36bfe8a13d07200dc61d460eea374465146f05f13171189b5f5aa789946cdefa

See more details on using hashes here.

Provenance

The following attestation bundles were made for brightbean-1.0.0rc4.tar.gz:

Publisher: python-release.yml on JanSchm/social-intelligence-app

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file brightbean-1.0.0rc4-py3-none-any.whl.

File metadata

  • Download URL: brightbean-1.0.0rc4-py3-none-any.whl
  • Upload date:
  • Size: 80.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for brightbean-1.0.0rc4-py3-none-any.whl
Algorithm Hash digest
SHA256 bfaabe5e770f0ccb8a330ae16e9caa660cc1ee09917bc9177500fa2e04b69140
MD5 6ee958d46ede8054943b7f0f173950ae
BLAKE2b-256 9dabec5901ab0cf22d1ca45fcad678e9a18681a5f7c08821f0e4913f327aa42e

See more details on using hashes here.

Provenance

The following attestation bundles were made for brightbean-1.0.0rc4-py3-none-any.whl:

Publisher: python-release.yml on JanSchm/social-intelligence-app

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page