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_urlmust behttps://. Plainhttp://is accepted only for a loopback host (localhost,127.0.0.1,::1); for a trusted private network passallow_insecure_http=Trueor setHONEYFRAME_ALLOW_INSECURE_HTTP=1. Otherwise the client raisesHoneyframeConfigErrorbefore sending anything.verify=takesTrue(default), a CA bundle path (file or directory) for a private CA, or anssl.SSLContext.verify=Falseworks 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.requesthas 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", …}
Publish a webapp and share it by link
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 mostrow_limitrows (the platform caps it at 50,000).paramsare named binds:sql("… WHERE order_date >= :start", params={"start": "2026-01-01"}). When the result was cut atrow_limitthe SDK warns and setsdf.attrs["truncated"] = True. For full-table extracts useDataset.get_dataframe(),Dataset.to_parquet()orhoneyframe 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
DeprecationWarningfor 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.