Skip to main content

honeyframeapi

The official Python client for the Honeyframe data platform. Script the platform from a notebook, a job or a CI pipeline: read data into pandas, author and build warehouse models, publish dashboards and webapps, run scenarios, and manage connectors, knowledge banks and API services.

Documentation: https://docs.honeyframe.io/ — this page covers the SDK; the portal covers the platform behind it (Datasets, Recipes, Flow, Dashboards, Webapps, Scheduler, API).

from honeyframeapi import HoneyframeClient

client = HoneyframeClient("https://platform.your-domain.com", token="hf_…", project_id=1)
df = client.sql("SELECT region, SUM(amount) AS total FROM sales GROUP BY region")

One client, then project / dataset / dashboard handles hanging off it.

Install

pip install honeyframeapi              # core (httpx only)
pip install "honeyframeapi[pandas]"    # + pandas for get_dataframe() / sql()
pip install "honeyframeapi[parquet]"   # + pandas and pyarrow for to_parquet()

Python 3.9 or newer. Installing the package also installs the honeyframe command-line tool (see CLI).

Authenticate

The recommended credential is a Personal Access Token (PAT) — a long-lived hf_… token you mint from the platform UI (avatar menu → Personal access tokens) or with client.create_pat(). Unlike the short-lived session token it does not expire on the hour, so it is the right fit for scripts and automation.

import os
import honeyframeapi

# 1. a Personal Access Token (recommended) — read it from the environment or
#    a secret store, never from a literal in the script
client = honeyframeapi.HoneyframeClient(
    "https://platform.your-domain.com",
    token=os.environ["HONEYFRAME_TOKEN"],
    project_id=1,
)

# 2. email / password for interactive use — the client signs in and signs in
#    again when the session expires; prefer a PAT for unattended jobs
import getpass
client = honeyframeapi.HoneyframeClient(
    "https://platform.your-domain.com",
    username="you@your-domain.com", password=getpass.getpass(),
    project_id=1,
)

# 3. from environment variables
#    HONEYFRAME_URL, HONEYFRAME_TOKEN (or HONEYFRAME_USERNAME + HONEYFRAME_PASSWORD),
#    HONEYFRAME_PROJECT_ID
client = honeyframeapi.HoneyframeClient.from_env()

The older HUB_API_BASE, HUB_USERNAME, HUB_PASSWORD and HUB_PROJECT_ID names are still read as fallbacks but are deprecated: using one emits a DeprecationWarning. Rename them to the HONEYFRAME_* names.

client.create_pat() mints a token that expires after 90 days by default (expires_days=0 for one that never expires, up to 730 days otherwise).

Connection security

  • base_url must be https://. Plain http:// is accepted only for a loopback host (localhost, 127.0.0.1, ::1); for a trusted private network pass allow_insecure_http=True or set HONEYFRAME_ALLOW_INSECURE_HTTP=1. Otherwise the client raises HoneyframeConfigError before sending anything.
  • verify= takes True (default), a CA bundle path (file or directory) for a private CA, or an ssl.SSLContext. verify=False works but warns.
  • Redirects are never followed, so the token cannot be forwarded to another host. Every value the SDK puts into a URL path (dataset names, keys, slugs) is percent-encoded, so a name containing /, ?, # or .. cannot re-route a request.
  • Exceptions never carry credentials: exc.response.request has no Authorization header or body, and a failed login keeps no response at all.
  • Files the SDK writes for you (exports, bundle / folder / artifact downloads, PDFs, notebooks) are created readable by your user only (0600).

Author the warehouse, end to end

One governed loop that adapts to the deployment:

proj = client.get_project(1)
proj.engine()                      # 'dbt' or 'native' — does this install have dbt?
proj.upload_dataframe("raw_sales", df, materialize=True)   # push local data in
if proj.uses_dbt():
    proj.dbt.create_model("stg_sales", sql="SELECT … FROM {{ ref('raw_sales') }}")
    proj.dbt.run(select="stg_sales")   # author + build a dbt model
df = proj.sql("SELECT * FROM stg_sales")                   # read results back
proj.get_dataset("raw_sales").upstream()                   # trace lineage

# …then share it: dashboard → webapp → token-gated public link
dash = proj.create_dashboard("Sales")
dash.add_card("By region", "bar", "SELECT region, total FROM stg_sales")
app  = client.publish_webapp("sales", "Sales", config={"pages": [...]})
print(app.create_public_link(expires_days=7)["share_url"])   # default: 30 days

On a native (non-dbt) install the authoring lines change — check proj.engine() — but the upload → read → dashboard → webapp → share steps are the same.

Read data into pandas

# ad-hoc SQL in the current project
df = client.sql("""
    SELECT channel, COUNT(*) AS orders
    FROM marts.fact_orders
    GROUP BY channel ORDER BY orders DESC
""")

# or read a whole dataset
proj = client.get_project(1)
df = proj.get_dataset("fact_orders").get_dataframe()
head = proj.get_dataset("fact_orders").head(20)
stats = proj.get_dataset("fact_orders").profile()

Build a dashboard from code

proj = client.get_project(1)
dash = proj.create_dashboard("Orders overview",
                             description="Order channels and volume")

dash.add_card("Total orders", "kpi",
              "SELECT COUNT(*) AS n FROM marts.fact_orders",
              card_config={"color_theme": "ocean"}, size_x=6, size_y=2)

dash.add_card("Orders by channel", "donut",
              "SELECT channel AS name, COUNT(*) AS value "
              "FROM marts.fact_orders GROUP BY channel",
              card_config={"nameField": "name", "valueField": "value"})

results = dash.execute_all()
print("Open:", dash.url)

Already have card definitions as dicts? Pass them straight through:

dash.add_cards(cards)   # list of {"title", "card_type", "sql_query", "card_config", …}

Wrap dashboard cards in a webapp and hand a stakeholder a URL they can open without an account (token-gated, PII-masked).

app = client.publish_webapp(
    "orders-overview", "Orders overview",
    config={"pages": [{"key": "home", "title": "Home", "blocks": [
        {"kind": "card_ref", "dashboard_id": dash.dashboard_id,
         "card_id": dash.cards[0].card_id},
    ]}]},
)

link = app.create_public_link(label="exec share", expires_days=30)  # 1..365; 0 = never
print(link["token"])                       # shown ONCE — store it
print(app.public_url(link["token"]))       # anonymous URL

# later: audit + revoke
for l in app.public_links():
    print(l["label"], l["status"], l["view_count"])
app.revoke_public_link(link["id"])

Public links expire after 30 days unless you pass another expires_days (1–365); expires_days=0 makes a link that never expires.

publish_webapp is project-scoped — set project_id on the client first. If your webapps are served from a different host than the API, pass it: app.public_url(token, host="https://apps.your-domain.com").

Build a multi-page app from code

The builders compose pages, blocks and filters for you — including two flexible block kinds: code_card (a governed read-only SELECT rendered as a chart, run server-side with an 8 s timeout and a 5,000-row cap) and html_block (freeform HTML/CSS/JS). With mode="sandboxed" the block runs in an opaque-origin iframe whose CSP blocks outbound network, so its script cannot read the viewer's session or call out. Add data_sql= to a sandboxed block and the server runs that governed SELECT and posts the rows into the iframe — your custom JS renders real warehouse data without any API access of its own.

from honeyframeapi import (
    page, nav_item, code_card, html_block, card_ref,
    filters, date_range_filter, entity_filter, pill_multi_filter,
    metric_search_filter, chrome,
)

app = client.create_webapp(
    "orders-overview", "Orders overview",
    pages=[page(
        "home", "Home",
        blocks=[
            card_ref(dash.dashboard_id, dash.cards[0].card_id, w=12, h=4),
            code_card("SELECT store_name, orders FROM marts.v_store_orders "
                      "WHERE :period_start <= order_date AND order_date <= :period_end",
                      "bar", x=12, w=12, h=4, title="Orders by store"),
            # a sandboxed block that renders REAL data: the server runs
            # data_sql and posts the rows into the iframe
            html_block("<div id=viz></div><script>addEventListener('message',e=>{"
                       "if(e.data?.source==='honeyframe')draw(e.data.rows)})</script>",
                       mode="sandboxed", w=12, h=8, title="Custom viz",
                       data_sql="SELECT store_name, orders FROM marts.v_store_orders"),
            html_block("<i>internal note</i>", public_hidden=True, w=12, h=2),
        ],
        # page-level filters, bound into the SQL above by parameter name
        filters=filters(
            metric=metric_search_filter(),                  # finds a card, binds nothing
            period=date_range_filter(locale="en", timezone="UTC"),  # :period_start / :period_end
            store=entity_filter("store_codes", mode="multi", chips=True,
                                options_sql="SELECT store_code AS value, store_name AS label "
                                            "FROM marts.dim_store"),
            # a fixed list → inline pills; binds :tier and :tier_codes
            tier=pill_multi_filter("tier", ["A", "B", "C"], label="Store tier",
                                   empty_value="ALL"),
        ),
    )],
    nav=[nav_item("home", "Home", icon="gauge")],
    # header/rail affordances — each one is off until you turn it on
    chrome=chrome(show_sidebar_search=True, show_clock=True, show_theme_toggle=True),
    publish=True,
)

# incremental edits read-modify-write the stored config for you
app.add_page(page("detail", "Detail", [code_card("SELECT 1", "kpi")]),
             nav=nav_item("detail", "Detail"), publish=True)
app.set_theme({"primaryColor": "#0E7C66"})
app.remove_page("detail")

Scenarios, jobs and recipes

proj = client.get_project(1)

# scenarios — trigger and wait
sc = proj.get_scenario("daily_pipeline")
final = sc.run_and_wait()                 # polls to a terminal state
print(final["status"], final.get("steps_failed"))

# jobs / pipeline
runs = client.jobs.list_runs(status="failed", limit=50, offset=0)   # limit ≤ 200
client.jobs.trigger("dbt_run", select="stg_sales+")              # returns at once
run = client.jobs.trigger_and_wait("dbt_run", select="stg_sales+")  # blocks
print(run["status"], client.jobs.logs("dbt_run", lines=100))

# dbt builds: wait=True blocks until the run finishes and returns it
proj.run_dbt("stg_sales+", wait=True)
proj.get_recipe("fact_orders").build(downstream=True, wait=True)

# datasets — point at an existing table
proj.create_dataset("orders", connector_id=5,
                    connection_config={"schema_name": "sales", "table_name": "orders"})

# datasets — surface existing raw tables in the picker (no data copied)
proj.register_source_table("staging", "orders_raw", connector_id=5)
proj.register_schema("staging", connector_id=5)   # or the whole schema, idempotent

# connectors (org-level)
conn = client.create_connector("ERP replica", "postgresql", config={"host": "…"})
print(conn.test())

# publish a data API + mint a key (90-day expiry, sensitivity_max="medium"
# by default: high/critical columns come back [BLOCKED]); store the key in a
# secret manager — it is shown once
asset = client.create_asset("data_api", "orders", "Orders API", config={...})
asset.publish()
key = asset.create_api_key(name="readonly")

# ask an agent
print(client.get_agent(42).ask("Cancellations last month?")["answer"])

CLI

Installing the package also installs a honeyframe command. It reads the same environment variables as HoneyframeClient.from_env(). Flags go after the subcommand (kubectl/gh style).

export HONEYFRAME_URL=https://platform.your-domain.com
export HONEYFRAME_TOKEN=hf_…            # a Personal Access Token

honeyframe whoami
honeyframe projects -f csv
honeyframe datasets -p 1
honeyframe sql "SELECT COUNT(*) FROM marts.fact_orders" -p 1 -f csv
honeyframe engine -p 1                       # dbt or native install?
honeyframe upload sales sales.csv --materialize -p 1
honeyframe lineage fact_orders --down -p 1
honeyframe export fact_orders out.parquet --max-rows 100000 -p 1
honeyframe pat create --name laptop --expires-days 90
honeyframe scenario run daily_pipeline -p 1 --wait

To pass a token explicitly, use --token-file PATH or --token - (read from stdin); a literal --token VALUE still works but warns, because it shows up in the process list and shell history. honeyframe pat create prints the new token once on stdout (as JSON, for scripts) with a reminder on stderr to store it. honeyframe plugin run executes plugin code in-process as you (it is not sandboxed) and hides HONEYFRAME_* / HUB_* variables from it unless you pass --pass-env.

Resource map

Handle Get it from Key methods
HoneyframeClient constructor sql, request, list_projects, get_project, get_dashboard, jobs, list_connectors, create_connector, get_connector, list_my_credentials, list_knowledge_bases, create_knowledge_base, get_knowledge_base, kb_connector_options, llm_mesh_overview, get_llm_policy, set_llm_policy, llm_mesh_usage, list_card_templates, create_card_template, list_collections, create_collection, update_collection, delete_collection, list_assets, create_asset, get_asset, list_webapps, get_webapp, publish_webapp, list_agents, get_agent, chat, get_auth_info
Project client.get_project(id_or_slug) list_datasets, get_dataset, create_dataset, import_dataset, register_source_table, register_schema, sql, create_dashboard, list_scenarios, get_scenario, create_scenario, get_recipe, run_dbt, change_recipe_engines, create_bundle, list_bundles, bundle, list_signoffs, list_bundle_activations, dbt → DbtIDE, get_preferences, set_preferences, list_connectors
Dataset project.get_dataset(name) get_dataframe, head, profile, statistics, get_metadata, ask, materialize, versions, preview_version, rollback_version, update_version, delete
DatasetStatistics dataset.statistics one method per EDA card — univariate, correlation, scatter_fit, quantile_table, cdf_plot, fit_distribution, shapiro_wilk, chi2_independence_test, ttest_2samp, mann_whitney_u, paired_ttest, anova_1way, levene_test, ks_test_2samp, acf_plot, mann_kendall_test, pca — plus compute(card_type, **params), list_worksheets, create_worksheet, get_worksheet
StatisticsWorksheet dataset.statistics.create_worksheet(name) cards, add_card, update_card, remove_card, set_cards, rename, compute_card, compute_all, reload, delete
Dashboard project.create_dashboard(...) / client.get_dashboard(id) add_card, add_cards, execute_all (filters=[...]), cards, update, move_to_collection, public_links, create_public_link, revoke_public_link, export_pdf, shares, add_share, update_share, remove_share, revisions, create_revision, get_revision, diff_revisions, restore_revision, undo, undo_card, url
Card dashboard.cards / dashboard.add_card(...) execute, update, delete, unlink_template
CardTemplate client.create_card_template(...) / client.list_card_templates() update, delete, insert_into, link_into, preview, duplicate, versions, get_version, restore_version
Connector client.create_connector(...) / client.get_connector(id) test, update, delete, set_my_credentials, delete_my_credentials, rotate, secret_history, rollback_secret
Scenario project.get_scenario(id_or_name) run, run_and_wait, get_last_runs, update, delete, list_triggers, add_trigger, update_trigger, delete_trigger, list_reporters, add_reporter, update_reporter, delete_reporter, test_reporter
Recipe project.get_recipe(model_name) get_metadata, runs, build, engine_status, save_prepare_steps, save
Bundle project.create_bundle(...) / project.bundle(version) download, activate, deploy, push, governance_status, request_signoff, submit_feedback, submit_for_approval, approve, reject, abandon
KnowledgeBase client.create_knowledge_base(...) / client.get_knowledge_base(id) update, delete, docs, upload_doc, remove_doc, embed, sync, clear, search, check_vector_store, create_tool, link_agent, unlink_agent, usage
JobsAPI client.jobs list_runs, get_run, wait, abort_run, trigger, trigger_and_wait, list_pipeline_runs, get_pipeline_run, logs
PublishedAsset client.create_asset(...) / client.get_asset(id) publish, unpublish, update, create_api_key, list_api_keys, revoke_api_key, versions, rollback, diff_versions
APIService client.create_api_service(...) / client.get_api_service(id) add_endpoint, set_endpoints, publish, create_key, call, test, versions, rollback, diff_versions
Webapp client.publish_webapp(...) / client.get_webapp(key) config, public_links, create_public_link, revoke_public_link, versions, rollback, diff_versions, url, public_url
DbtIDE project.dbt write_file, read_file, create_model, list_models, compile, run, test, build, git_commit
Agent client.get_agent(id) ask, ask_stream

Global-filter builders (from honeyframeapi import ...): dashboard_filter, filter_between, filter_compare, filter_in — build the filters=[...] list Dashboard.execute_all applies to every card whose SQL references the filter column.

Errors and retries

Every failure raises a subclass of honeyframeapi.HoneyframeError: HoneyframeAuthError (401/403), HoneyframeNotFoundError (404), HoneyframeValidationError (400/409/422), HoneyframeRateLimitError (429, with .retry_after), HoneyframeBudgetExceededError (the org's metered budget is spent; not retryable) and HoneyframeAPIError (other non-2xx), each carrying .status_code and .detail (the server's message, capped at 4 KB). A missing or unsafe setting (no URL, a plain-http URL, no project) raises HoneyframeConfigError before any request is sent.

Long calls take their own timeout= in seconds (Dashboard.export_pdf, Dashboard.execute_all, Dataset.materialize, ML recipe run), overriding the client's timeout (default 60 s).

Idempotent requests (GET, HEAD, OPTIONS) are retried on transport errors and on 502/503/504 with exponential backoff; writes and 429s are never retried automatically. Tune with max_retries (default 2) and retry_backoff (default 0.5 s), or pass max_retries=0 to turn it off.

Scope

Covered today: authentication (PAT, session login), projects, datasets to pandas plus create / import / materialize, snapshot versions and as-of reads, ad-hoc SQL, dataset statistics, dashboards and cards, webapps and public links, connectors (full CRUD and test), knowledge banks (the RAG build loop), LLM governance and spend, scenarios (run, wait, history, triggers, reporters), project bundles with deployment sign-offs, jobs and pipeline triggers, recipes (inspect, dbt build, prepare steps, Recipe.save, per-recipe engine selection), published assets and data-API keys, API services, and agent chat.

Also in the package, documented on the portal: machine learning (train, score, cluster, evaluate, saved models), API services, streaming chat and push notifications, managed folders, dataset metrics and checks, experiments, notebooks, wiki pages, and plugin recipes (honeyframe plugin …).

client.sql() note: sql(query, connector_id=None, row_limit=10000, params=None) runs through the platform's governed SQL endpoint and returns at most row_limit rows (the platform caps it at 50,000). params are named binds: sql("… WHERE order_date >= :start", params={"start": "2026-01-01"}). When the result was cut at row_limit the SDK warns and sets df.attrs["truncated"] = True. For full-table extracts use Dataset.get_dataframe(), Dataset.to_parquet() or honeyframe export.

Calling an endpoint without a typed method

client.request(method, path, **kwargs) calls any platform endpoint with the same authentication, org/project headers, retries and typed exceptions as the typed methods:

client.request("GET", "/api/traces/sessions", params={"limit": 50})
client.request("POST", "/api/some/endpoint", json={...}, timeout=300)

path must start with / (an absolute URL is refused). Values you interpolate into path yourself are not encoded for you.

Versioning and stability

honeyframeapi is 0.x and follows semantic versioning in spirit: a minor release (0.1 → 0.2) may change behaviour, a patch release does not, and every change is listed in the changelog.

  • The public API is what honeyframeapi.__all__ exports, plus the methods and properties reachable from those objects. Names starting with _ and the handle constructors (Project(...), Dataset(...), …; get handles from the client instead) are private and may change without notice.
  • A deprecated name or argument keeps working and emits a DeprecationWarning for at least one minor release before it is removed.
  • Behaviour changes that tighten security (for example shorter default expiries) are called out under "Changed" in the changelog.

Support

Documentation and guides: https://docs.honeyframe.io/.

Metadata

Release files for honeyframeapi 0.1.8

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

Source distribution (sdist)

Source distribution for honeyframeapi 0.1.8
File Size Uploaded Provenance Actions
honeyframeapi-0.1.8.tar.gz 139.8 kB Without provenance Details for honeyframeapi-0.1.8.tar.gz

Built distribution (wheel)

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

Total release size: 288.4 kB

Release files / honeyframeapi-0.1.8.tar.gz

Download URL honeyframeapi-0.1.8.tar.gz
Size 139.8 kB
Tags Source
SHA-256 checksum
How to use checksums
5eff90690a57e14b3c8ee85268356175555f86715f9aa59d7f658c4b3fc9c075
BLAKE2b-256 checksum
How to use checksums
011250e6c80eb503afea91fc9105edaf500ae612729527aecd81e320371e8f8b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Release files / honeyframeapi-0.1.8-py3-none-any.whl

Download URL honeyframeapi-0.1.8-py3-none-any.whl
Size 148.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c0799387e5fa11aa33a5feb9dd0c863fee4ec162494233be59dc7b4a555c406e
BLAKE2b-256 checksum
How to use checksums
045e2cdd0981e9a443604e98ec17ac7c29dd304eb45109911dd2d36e31f43c04
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Security for honeyframeapi 0.1.8

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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