Skip to main content

hub-equity: Python SDK

Typed Python client for the Hub-Equity financial-data API: machine-readable XBRL / iXBRL from primary filings, SEC EDGAR (US) and ESEF (Europe), normalized into canonical financial concepts, with the provenance preserved.

What sets the data apart, and what this SDK exposes:

  • Source-linked. Every value carries the filing it came from (form_type, filing_date, a viewer URL). You can trace any number back to the filer's document, not a black box that silently rewrites as-filed history.
  • XBRL-native. Calculation linkbase (with filer weights and verifier-corrected weights), dimensional structure, and the raw filer-declared facts, not just flattened labels.
  • Restatement-aware. Per-concept deltas across 10-K/A amendments and across silent restatements, the figures a later filing reprinted differently with no amendment filed.
  • Cross-issuer, calendar-aligned. compare matches periods by calendar year (Bloomberg / FactSet convention), so Apple (Sep FYE) and a Dec-FYE peer line up.

The SDK is a thin, typed wrapper over the public REST API: it ships no data and no secrets; you bring your own API key. Full API reference: https://docs.hub-equity.com. It covers a subset of the API: fact decomposition, segments, the amendment summary, cross-period comparison, roll-up, currency conversion, the entity quality grade, validation results, extension concepts and the hub catalog are read over REST.

Maturity. The data engine and public REST API behind this SDK run in production and power Hub-Equity's own chat. This PyPI package is a newly published client for that API; the surface is stable (Semantic Versioning, 2.x), but as a distributed package it is fresh, hence the Beta classifier.

Install

pip install hub-equity
# Optional pandas helpers:
pip install 'hub-equity[pandas]'

Python 3.10+. Runtime deps: httpx, pydantic.

Authentication

from hub_equity import HubEquity

# Explicit key
client = HubEquity(api_key="INSERT_YOUR_API_KEY")

# Or set HUBEQUITY_API_KEY env var (recommended for notebooks)
client = HubEquity()

A key is required: the client refuses to start without one (ValueError), since the API answers 401 AUTH_REQUIRED to a call without a key. A Free key takes a minute at https://hub-equity.com/settings/api-keys (shown once, store it securely). Set HUBEQUITY_API_URL to point the client at a non-default host (defaults to https://api.hub-equity.com).

Entity arguments accept a Hub-Equity UUID, a ticker (e.g. "AAPL") or TICKER.MIC (e.g. "MC.XPAR") on every entity method, compare included. An ambiguous ticker answers 409 with the suffixes that disambiguate it.

Quickstart

from hub_equity import HubEquity

with HubEquity() as client:
    fin = client.get_financials("MC.XPAR", years=5)          # LVMH, every statement
    revenue = fin.metric("REVENUE")
    print([(v.fiscal_year, v.display_value) for v in revenue.values])

    rev = client.get_metric_history("AAPL", "REVENUE", years=5)
    print(f"{rev.entity_name}: {rev.hub_label} (CAGR {rev.cagr_pct:.1f}%)")
    for p in rev.points:
        print(f"  FY{p.fiscal_year}: {p.value:,.0f} {p.currency}"
              f"  ({p.yoy_growth_pct:+.1f}% YoY)  ← {p.source.form_type} {p.source.filing_date}")
        # p.source.viewer_url / p.source.external_url -> trace back to the filing

Methods

Area Method Returns
Entities find_entity(query) FindEntityResult: up to 5 ranked matches (name, ticker, TICKER.MIC, CIK)
search_entities(q, ...), iter_entities(q, ...) SearchResponse / generator of SearchResult
get_entity_profile(entity) EntityProfile: identifiers, DEI, auditor, latest filings
Statements get_financials(entity, *, years=3, category='all', currency=None, end_year=None) Financials: periods + metrics; cite MetricValue.display_value
Facts get_fact(entity, code, fiscal_year, fiscal_period_type='FY') Fact: one value + its source filing (HubEquityError 404 when none is served)
Screener screen_companies(*, country, sector, min_revenue, ..., sort_by, limit=20) ScreenCompaniesResult (Free: 20 rows, no quality filter)
Time-series get_metric_history(entity, code, *, years=5, period_type='FY') MetricHistory: values + YoY + CAGR
As-of get_metric_as_of(entity, code, as_of, *, years=5, period_type='FY') AsOfResponse: series read from the filings filed on or before a date (beta: can differ from the served values)
Compare compare(entity_ids, hub_concept_codes, fiscal_year, fiscal_period_type='FY') CompareEntitiesResult: N×M matrix
Calc linkbase get_calculation_sections(filing_id) CalculationSectionsResponse: calc roles
get_calculation_tree(filing_id, link_role) CalculationTree: edges + weights
Dimensions get_entity_dimensions(entity) EntityDimensionsResponse: axes + members
Raw facts get_filing_facts(filing_id, *, section, concept, is_extension, limit, offset), iter_filing_facts(filing_id, *, ..., per_page=200) RawFactsResponse / generator of RawFact (Free: structure without the values)
Restatements get_restatement_diff(entity, *, fiscal_year, hub_concept_code, min_diff_pct=0.01, kind, source='amendments') AmendmentDiffResult (source='comparatives': silent restatements)
Quality get_filing_quality_grade(filing_id) QualityGradeResponse: A-F + 4 drivers
Concepts search_concepts(q, *, taxonomy, category, limit, offset, cursor) ConceptSearchResponse
iter_concepts(q, *, taxonomy, category, page_size=100) generator of ConceptSummary
get_concept(hub_concept_code) ConceptDetail: forward catalog entry (label, description, category, taxonomy scope)
Exports (Team, Enterprise) create_export(entity_ids, *, years=3, category='all', xbrl='none'), list_exports, get_export(job_id) ExportJob
download_export(job_id, path=None) zip bytes, or the Path written
Webhooks (Enterprise) create_webhook(name, url, *, events, entity_ids), rotate_webhook_secret(id) WebhookCreated (secret shown once)
list_webhooks, get_webhook(id), update_webhook(id, ...), delete_webhook(id) Webhook
list_webhook_deliveries(id, *, limit=50) list[WebhookDelivery]
send_webhook_test(id), redeliver_webhook_delivery(id, delivery_id) WebhookAttempt

Every method returns a typed pydantic model (autocomplete + validation), with extra="allow" so a newer API never breaks an older SDK.

Compare issuers (calendar-year aligned, premium)

matrix = client.compare(
    entity_ids=["AAPL", "MSFT"],
    hub_concept_codes=["REVENUE", "NET_INCOME"],
    fiscal_year=2024,
)
for row in matrix.rows:
    cell = row.cells.get("REVENUE")
    if cell:
        print(f"{row.name} ({row.ticker}): {cell.value:,.0f} {cell.currency}"
              f"  [{cell.source.form_type if cell.source else ''}]")

Audit a number to its filing

# Drill to the raw filer-declared facts (qname, unit, decimals, dimensions).
# The values come on a paid plan; a Free key reads the structure, values null.
facts = client.get_filing_facts("<filing-uuid>", concept="Revenue", limit=50)
for f in facts.facts:
    print(f.concept_qname, f.value_numeric, f.unit, f.dimensions)

pandas

import pandas as pd

hist = client.get_metric_history("MSFT", "REVENUE", years=10)
df = pd.DataFrame([p.model_dump() for p in hist.points])
df["source_form"] = [p.source.form_type for p in hist.points]

Background exports (Team, Enterprise)

import time

job = client.create_export(["AAPL", "MC.XPAR"], years=5)
while job.status in ("pending", "running"):
    time.sleep(10)
    job = client.get_export(job.id)
if job.status == "ready":
    client.download_export(job.id, path="export.zip")  # follows the 307 to the signed URL
else:
    print(job.error)

Webhooks (Enterprise, SDK 2.0.0)

hook = client.create_webhook("prod", "https://example.com/hub-equity")
SECRET = hook.secret                      # shown once: store it now
client.send_webhook_test(hook.id)         # a signed ping, answered with your endpoint's status
client.list_webhook_deliveries(hook.id)   # what was sent, skipped or retried

A subscription created in /settings/webhooks (or create_webhook) delivers signed POSTs to your endpoint: filing.ingested when a filing's values are served, restatement.material when a later filing restates a value you may have used. Verify the raw body, not a re-serialised parse:

from hub_equity import verify_webhook_signature

@app.post("/hub-equity")
async def receive(request):
    raw = await request.body()
    if not verify_webhook_signature(SECRET, request.headers, raw):
        return Response(status_code=401)
    event = json.loads(raw)          # {"id", "event", "created_at", "data"}
    ...
    return Response(status_code=204)  # answer 2xx within 10 s

The check is constant-time, rejects a timestamp more than five minutes from your clock, and accepts either signature during the 24 hours that follow a secret rotation (v1=<new>,v1=<old>). Deduplicate on the X-Hub-Equity-Delivery header: delivery is at-least-once.

Reliability

  • Retries 429 (rate limit) + 5xx (server) with exponential backoff (3 retries, base 1 s, cap 10 s); honors Retry-After when present.
  • After exhausting retries on 429, raises RateLimitError (subclass of HubEquityError) with the last retry_after attached. A 429 whose Retry-After exceeds the 10 s cap (a daily or monthly allowance used up) is raised at once, not retried.
  • Other 4xx raise HubEquityError immediately (no retries), with .status_code.
  • The exception message carries the API's own error.code and error.message (e.g. which suffix disambiguates a ticker, when a quota resets).
from hub_equity import HubEquity, HubEquityError, RateLimitError

with HubEquity() as client:
    try:
        rev = client.get_metric_history("AAPL", "REVENUE")
    except RateLimitError as exc:
        print(f"Rate-limited, retry after {exc.retry_after}s")
    except HubEquityError as exc:
        print(f"{exc.status_code}: {exc}")

Rate limits

Caller Per minute
Free key 120
Builder key 300
Team key 600
Enterprise key 1 000

Bucketed per API key.

Volume allowances are counted per workspace on API keys and refuse with a 429 (DAILY_QUOTA_EXCEEDED / MONTHLY_QUOTA_EXCEEDED, headers X-Quota-*, resets_on in the body): Free 200 calls a day and 5 000 a month, Builder 50 000 a month, Team 500 000 a month, Enterprise unlimited.

Premium endpoints (compare, compare-filings, as-of, dimensions, restatement diff, calculation tree, validation results, extension concepts) require a paid key (403 SCOPE_MISSING on a Free key, 401 without a key). A Free key is also capped on /decomposition (depth=1) and on /companies/screen (20 results, no min_quality_grade), gets the axes of /segments/{code} without their members, and reads the raw facts of /filings/{id}/facts without their values.

Versioning

The SDK follows Semantic Versioning: a breaking change ships only in a major version. Pin a major version in production (hub-equity>=2,<3). Response models use extra="allow", so additive API changes ship in minor releases without breaking older SDK installs.

2.0.0 breaks two things, because the API itself changed: HubEquity() without a key raises ValueError (the API refuses keyless calls, so the api_key="" examples of 1.0 and 1.1 no longer run), and CompareCell.extraction_method is removed. See the CHANGELOG.

License

Apache-2.0. See LICENSE.

Metadata

Release files for hub-equity 2.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 hub-equity 2.0.0
File Size Uploaded
hub_equity-2.0.0.tar.gz 43.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for hub-equity 2.0.0
File Interpreter ABI Platform
hub_equity-2.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 71.4 kB

Release files / hub_equity-2.0.0.tar.gz

Download URL hub_equity-2.0.0.tar.gz
Size 43.2 kB
Tags Source
SHA-256 checksum
How to use checksums
d3eee6c9e210d4bffa678c35f9a84f3a25773f86e6db881cdb8da65f88aeadf3
BLAKE2b-256 checksum
How to use checksums
501304d2bcbfce2920b63d30e9d2f9d5ff3a0141080a0f1ef68bf1f34a4ed454
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release files / hub_equity-2.0.0-py3-none-any.whl

Download URL hub_equity-2.0.0-py3-none-any.whl
Size 28.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d257aa3de36a05ff047b398df71ce8d40526f16b422188f6b724a15dbc6b5cc5
BLAKE2b-256 checksum
How to use checksums
bb8793ceca220920eadf5b15c64baaded8885665f1d8a3f58e81222adc1dbe29
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

2.0.0 This release

2 release files

1.1.0

2 release files

1.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