Lewati ke konten utama
Versi: v0.1.8

API Services

An API Service turns datasets, lookups, and saved ML models into callable REST endpoints. It is a single published asset (asset_type='api_service') that groups several key-gated HTTP endpoints under one published-asset row, served with minted API keys.

API Services reuse the same machinery as published datasets: the same published-asset lifecycle (create → publish → version), the same X-API-Key data-API key minting, and — for sql and lookup endpoints — the very same query executor that powers the data API (filters, joins, row-scope, PII masking, result cache). There is no separate "API node" to stand up; the serving routes run inside the platform service.

The schema change ships as the 2026-06-14_api_services.sql migration, which runs automatically on upgrade (registered in scripts/migrate.py). It widens the publishing asset types and the published_assets CHECK to allow api_service, and adds the api_keys.allowed_endpoints column — no new tables, since a service is just a published-asset row whose config.endpoints[] holds the endpoint definitions.

What is an API Service

A service's config holds a list of endpoints:

{
"endpoints": [
{ "id": "top", "kind": "sql", "query": { "source": { "schema": "marts", "table": "fact_x" } } },
{ "id": "customer", "kind": "lookup", "dataset": "marts.dim_customer", "key_column": "customer_key" },
{ "id": "risk", "kind": "model", "model_id": 12 }
]
}

Each endpoint has a string id (unique within the service) and a kind. Because a service is an ordinary published asset, publishing, versioning, and key minting all go through the generic publishing and data-API endpoints — this page documents only the service-specific surface on top of that.

API Services are project-scoped: a service must be created with a project in scope.

Endpoint kinds

There are four endpoint kinds. Three are served today; one is deferred.

KindMethodWhat it does
sqlGETRuns a governed, parameterized data-API query (query is a data-API query config — source, columns, filters, joins, pagination). Request filters arrive as query params.
lookupGETA filtered row fetch — fetch rows from dataset (schema.table) where key_column equals the request's key_param value (default id). Compiles to a single equality-filter data-API query.
modelPOSTOnline prediction against a saved model (model_id). Inline feature rows in, predictions out.
functionSpec'd but deferred — returns 501. See Deferred and limitations.

sql and lookup

Both reuse the data-API query executor verbatim. That means everything the data API enforces applies here too: declared filters, joins, per-key row-scope, PII masking, and the result cache. A lookup endpoint is sugar over sql{dataset, key_column, key_param, columns, max_results} is expanded internally into a one-filter query config (an equality match on key_column whose value comes from the named request param).

model

A model endpoint scores a saved model online. The request body is either a single feature set or a batch:

{ "features": { "age": 34, "plan": "premium" } }
{ "rows": [ { "age": 34 }, { "age": 51 } ] }

The response is { "predictions": [...], "model_id": <id>, "count": <n> }.

Scoring runs in a subprocess (ml_runner's predict action) rather than in-process — the compiled platform binary deliberately excludes sklearn/joblib from its import graph, so the estimator is loaded inside the subprocess. One subprocess per request is fine for low-QPS internal serving, not high throughput. Before the subprocess loads anything, the platform confirms the model_id belongs to the service's project (a 404 otherwise). See Machine learning for how models are trained and saved.

Authenticating and calling

Call an endpoint at:

GET /api/service/{service_key}/{endpoint_id}
POST /api/service/{service_key}/{endpoint_id}

Authenticate with a minted data-API key on the X-API-Key header (the same dk_… key family used by published datasets). Use GET for sql/lookup endpoints (filters / lookup key as query params) and POST for model endpoints (the JSON body carries the features).

# sql / lookup — GET with the lookup key as a query param
curl "https://platform.your-domain.com/api/service/sales-serving/customer?id=42" \
-H "X-API-Key: dk_xxxxxxxxxxxxxxxxxxxx"

# model — POST with a JSON body of features
curl -X POST "https://platform.your-domain.com/api/service/sales-serving/churn_risk" \
-H "X-API-Key: dk_xxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"features": {"tenure": 8}}'

Key validation, status gating, and rate limiting mirror the data API. A request is rejected when the key is invalid (401), inactive or expired (403), the asset is not an API service or not published (404), the rate limit is exceeded (429), or the requested kind is deferred (501).

Key scoping

A key can be scoped to a subset of a service's endpoints via api_keys.allowed_endpoints:

  • NULL / empty → the key reaches every endpoint in the service.
  • A list of endpoint ids → the key reaches only those endpoints; any other endpoint returns 403.

This lets you hand out a key that can call churn_risk but not customer, for example.

A key's masking level also flows through: an unmasked key acts as admin (no PII masking) for sql/lookup output, while a masked key sees the same redaction a viewer would. Per-key allowed_columns, sensitivity_max, and row_scope apply exactly as they do for the data API.

Test pane dry-run

Before minting a key or even publishing, you can exercise an endpoint with a JWT (not an API key):

POST /api/api-services/{asset_id}/endpoints/{endpoint_id}/test

This is the Test pane dry-run. It requires project.edit, runs admin-unmasked, and uses the same dispatch the live serving route uses — so what you see in the Test pane is what a key will get. It is gated to your own organization.

Building a service

The API Services section lives on the project settings page (next to Project Bundles), so you can manage services without leaving Settings. From there you can:

  1. Create a service (asset_type=api_service); the list shows a draft/published badge with a publish/unpublish toggle.
  2. Add endpoints in the per-service Manage panel — an endpoint editor for the sql, lookup, and model kinds (read-modify-write the config), plus remove.
  3. Publish to make the endpoints callable.
  4. Mint endpoint-scoped keys — a per-endpoint checkbox scope (none checked = all endpoints), a copy-once reveal banner, and a list with revoke.
  5. Test inline — pick an endpoint, send JSON (body for model, query params for sql/lookup) to the /test dry-run, and see the JSON result.

Each step maps onto the generic published-asset operations: creating and editing the service is a published-asset create/update (its config.endpoints[]), publishing is the asset publish, and minting a key is a data-API key mint with allowed_endpoints set.

From the SDK

honeyframeapi 0.1.2 adds an APIService handle (a subclass of PublishedAsset) plus endpoint builders, completing the create → publish → mint-key → call round-trip from Python. See Developer → SDK.

The endpoint builders compose a service config without hand-rolling dicts:

BuilderProduces
sql_endpoint(id, query)A sql endpoint over a data-API query config.
lookup_endpoint(id, dataset, key_column, key_param="id", columns=None, max_results=100)A lookup endpoint.
model_endpoint(id, model_id=...)A model endpoint over a saved model.
function_endpoint(id, handler)A function endpoint — not served yet (501).

On the APIService handle:

  • add_endpoint(endpoint, publish=False) / set_endpoints([...]) — read-modify-write the config. Editing the draft does not republish unless you ask.
  • publish() / unpublish() — inherited from PublishedAsset, chainable.
  • create_key(name=, allowed_endpoints=, rate_limit=, expires_days=, masking_level=, ...) — mint a dk_… key, optionally scoped to a subset of endpoints. The full secret is on the returned object's .key, shown once.
  • call(endpoint_id, payload, api_key=, params=, method="POST") — hit the public serving route. Use method="GET" + params for sql/lookup, the default POST + payload for model.
  • test(endpoint_id, payload) — the JWT builder dry-run.

Create a service from the client (client.create_api_service(...), client.get_api_service(asset_id)) or project-scoped (project.create_api_service(...)).

End-to-end example

from honeyframeapi import (
HoneyframeClient, lookup_endpoint, model_endpoint,
)

client = HoneyframeClient(
base_url="https://platform.your-domain.com",
token="hf_xxxxxxxxxxxxxxxxxxxx",
)
project = client.get_project("sales")

# 1. create (draft) with two endpoints
svc = project.create_api_service(
"sales-serving", "Sales serving",
endpoints=[
lookup_endpoint("customer", "marts.dim_customer", "customer_key"),
model_endpoint("churn_risk", model_id=12),
],
)

# 2. publish — now the endpoints are callable
svc.publish()

# 3. mint a key — allowed_endpoints=None reaches every endpoint
key = svc.create_key(name="app")

# 4. call the model endpoint (the dk_… secret is on key.key, shown once)
out = svc.call("churn_risk", {"features": {"tenure": 8, "plan": "premium"}},
api_key=key.key)
print(out["predictions"])

# lookup endpoints are GET + query params
row = svc.call("customer", api_key=key.key, method="GET", params={"id": 42})

You can also dry-run an endpoint before minting any key:

svc.test("churn_risk", {"features": {"tenure": 8}})

Deferred and limitations

  • function endpoints are not available. The kind is spec'd and the builders accept it, but the serving runtime returns 501 for it — running an admin-curated handler body needs the sandboxed-execution design tracked separately. Do not depend on it yet.
  • Model serving is one subprocess per request. Fine for low-QPS internal serving; a persistent scoring worker is the documented next step. Don't put a high-throughput public workload behind a model endpoint today.
  • No new tables. Everything rides on published_assets + api_keys, so a service inherits the published-asset and data-API-key model wholesale — including their permission and org-scoping rules.

Endpoint reference

EndpointAuthDescription
GET /api/service/{service_key}/{endpoint_id}X-API-KeyServe a sql or lookup endpoint; filters / lookup key as query params.
POST /api/service/{service_key}/{endpoint_id}X-API-KeyServe a model endpoint; JSON body of features or rows.
POST /api/api-services/{asset_id}/endpoints/{endpoint_id}/testJWT (project.edit)Builder dry-run — admin-unmasked, no key, same dispatch as serving.

Service lifecycle (create / publish / version) and key minting use the generic publishing and data-API-key endpoints — see API and Developer → SDK.