Skip to main content
Version: Preview

SDK

The Marmot SDK is a typed client for the REST API, available in Python, Go and TypeScript. Authentication resolves automatically from environment variables, cached OAuth tokens or workload identity.

Install

pip install marmot-sdk

Requires Python 3.10+. Package name is marmot-sdk, import name is marmot.

Authenticate

Every SDK resolves credentials from the same priority chain, so the same code runs locally, in CI and in production without branching on environment:

  1. Explicit arguments. api_key / token passed to connect() or NewClient().
  2. Environment variables. MARMOT_API_KEY, MARMOT_TOKEN, MARMOT_HOST, MARMOT_CONTEXT.
  3. Cached OAuth token. Written to ~/.config/marmot/credentials.json by marmot login.
  4. Workload identity. GitHub Actions OIDC, GCP metadata or a Kubernetes service-account token. No API key needed.

If no credential resolves, the SDK raises an AuthError so misconfiguration fails fast.

Log in for local use:

marmot login http://localhost:5173

Then construct a client:

from marmot import AuthenticatedApiClient, UsersApi

# Resolves host and credential from the chain
client = AuthenticatedApiClient.connect()

# Or supply them explicitly
client = AuthenticatedApiClient.connect(
host="https://marmot.example.com", api_key="..."
)

me = UsersApi(client).get_users_me_sync()
print(me.name, "via", client.credential.source)

The following sections all assume client (and ctx for Go) is already constructed as shown above.

Python: one AuthenticatedApiClient is shared by every generated *Api class, which you construct around it — UsersApi(client), AssetsApi(client), and so on. Method names follow the operation: get_assets_id, post_lineage_batch. Each exists twice, as a coroutine and with a _sync suffix that runs it on a shared event loop, so the snippets below stay synchronous. Request bodies are pydantic models from marmot.generated.models, and failures raise marmot.errors types (NotFoundError, AuthError, ValidationError, RateLimitError, ServerError) rather than returning a status code.

One unified search across assets, glossary terms, teams and data products. Returns a typed SearchResponse with facets, results and pagination.

from marmot import AuthenticatedApiClient, SearchApi

client = AuthenticatedApiClient.connect()

results = SearchApi(client).get_search_sync(q="orders", types=["Table", "Topic"], limit=20)
print(f"{results.total} matches")
for hit in results.results or []:
print(hit.name, "-", hit.type.value if hit.type else "unknown")

Marmot accepts both free-text queries and a structured query language (@type: "Table" AND @provider: "postgres"). See the Query Language guide for the full grammar.

Assets

Every catalog entry is an Asset. The Assets resource covers CRUD, lookup by natural key, search, summary aggregates and tag management.

Fetch by ID

from marmot import AssetsApi, AuthenticatedApiClient

client = AuthenticatedApiClient.connect()

asset = AssetsApi(client).get_assets_id_sync(id="01HX...")
print(asset.name, asset.mrn)

Lookup by natural key

When you know an asset by (type, service, name) but not its ID, lookup resolves it. find does the same but returns nil / None instead of raising on a miss.

from marmot import AssetsApi, AuthenticatedApiClient
from marmot.errors import NotFoundError

assets = AssetsApi(AuthenticatedApiClient.connect())

asset = assets.get_assets_lookup_type_service_name_sync(
type="Table", service="postgres", name="orders"
)

# A missing asset raises rather than returning None
try:
assets.get_assets_lookup_type_service_name_sync(
type="Table", service="postgres", name="nope"
)
except NotFoundError:
asset = None

Search and summary

from marmot import AssetsApi, AuthenticatedApiClient

assets = AssetsApi(AuthenticatedApiClient.connect())

hits = assets.get_assets_search_sync(
q="customer",
types=["Table"],
services=["postgres"],
tags=["pii"],
limit=50,
)
summary = assets.get_assets_summary_sync() # totals by type, provider, tag

Create, update, delete

from marmot import AssetsApi, AuthenticatedApiClient
from marmot.generated.models import CreateAssetRequest, UpdateAssetRequest

assets = AssetsApi(AuthenticatedApiClient.connect())

created = assets.post_assets_sync(
create_asset_request=CreateAssetRequest(
name="orders",
type="Table",
providers=["postgres"],
metadata={"owner": "data-eng"},
)
)

updated = assets.put_assets_id_sync(
id=created.id,
update_asset_request=UpdateAssetRequest(description="Customer orders"),
)
assets.delete_assets_id_sync(id=created.id)

Tag management

from marmot import AssetsApi, AuthenticatedApiClient
from marmot.generated.models import TagRequest

assets = AssetsApi(AuthenticatedApiClient.connect())

assets.post_assets_tags_id_sync(id=asset_id, tag_request=TagRequest(tag="pii"))
assets.delete_assets_tags_id_sync(id=asset_id, tag_request=TagRequest(tag="pii"))

Lineage

Lineage edges identify endpoints by MRN (<type>://<service>/<name>). Read the graph from any node; write one edge or many at a time.

Read the graph

from marmot import AuthenticatedApiClient, LineageApi

lineage = LineageApi(AuthenticatedApiClient.connect())

graph = lineage.get_lineage_assets_id_sync(id=asset_id, direction="both", limit=50)
upstream = lineage.get_lineage_assets_id_sync(id=asset_id, direction="upstream", limit=10)

# Leave out edge types you don't want, e.g. structural CONTAINS edges
flow = lineage.get_lineage_assets_id_sync(id=asset_id, exclude_types="CONTAINS")

Write edges

Prefer /lineage/direct and /lineage/batch for new integrations. They accept simple (source, target) pairs and de-duplicate server-side.

from marmot import AuthenticatedApiClient, LineageApi
from marmot.generated.models import LineageEdge

lineage = LineageApi(AuthenticatedApiClient.connect())

# Single edge
lineage.post_lineage_direct_sync(
lineage_edge=LineageEdge(
source="postgres://prod/sales/orders",
target="kafka://prod/orders.events",
)
)

# Batched: one HTTP call, many edges
lineage.post_lineage_batch_sync(
lineage_edge=[
LineageEdge(
source="postgres://prod/sales/orders",
target="kafka://prod/orders.events",
),
LineageEdge(
source="kafka://prod/orders.events",
target="s3://prod/orders-archive",
),
]
)

Leave Type empty (DIRECT is the default) for code-derived edges; set it explicitly ("writes", "AGENT_LOOKUP", …) when you want to distinguish causes in the lineage graph.

Glossary

Business glossary terms with definitions, descriptions and hierarchies via parent_term_id.

from marmot import AuthenticatedApiClient, GlossaryApi
from marmot.generated.models import CreateTermRequest, UpdateTermRequest

glossary = GlossaryApi(AuthenticatedApiClient.connect())

page = glossary.get_glossary_list_sync(limit=50)
print(f"{len(page.terms or [])} of {page.total} terms")

hits = glossary.get_glossary_search_sync(q="customer")

term = glossary.post_glossary_sync(
create_term_request=CreateTermRequest(
name="PII",
definition="Personally Identifiable Information",
description="Data that can identify an individual.",
)
)

glossary.put_glossary_id_sync(
id=term.id,
update_term_request=UpdateTermRequest(name="Personally Identifiable Information"),
)
glossary.delete_glossary_id_sync(id=term.id)

Users & Teams

from marmot import AuthenticatedApiClient, TeamsApi, UsersApi

client = AuthenticatedApiClient.connect()
users, teams = UsersApi(client), TeamsApi(client)

me = users.get_users_me_sync()
user = users.get_users_id_sync(id=user_id)
page = users.get_users_sync(active=True, limit=100)

all_teams = teams.get_teams_sync()
team = teams.get_teams_id_sync(id=team_id)
members = teams.get_teams_id_members_sync(id=team_id)

API Keys

Manage personal API keys for the authenticated user. The full key token is only readable from the create response, so store it immediately.

from marmot import AuthenticatedApiClient, UsersApi
from marmot.generated.models import CreateAPIKeyRequest

users = UsersApi(AuthenticatedApiClient.connect())

keys = users.get_users_apikeys_sync()
created = users.post_users_apikeys_sync(
create_api_key_request=CreateAPIKeyRequest(name="ci-deploy", expires_in_days=30)
)
print(created.key) # only readable here
users.delete_users_apikeys_id_sync(id=created.id)

Runs

Read pipeline-ingestion run history. Useful when wiring up alerts on failed ingests or audit dashboards.

from marmot import AuthenticatedApiClient, RunsApi

runs = RunsApi(AuthenticatedApiClient.connect())

recent = runs.get_runs_sync(statuses="failed,running", limit=20)
run = runs.get_runs_id_sync(id=run_id)
entities = runs.get_runs_id_entities_sync(id=run_id, status="failed")

Metrics

Catalog usage and breakdown metrics. top_assets and top_queries take an inclusive [start, end] window of RFC3339 timestamps.

from marmot import AuthenticatedApiClient, MetricsApi

metrics = MetricsApi(AuthenticatedApiClient.connect())

total = metrics.get_metrics_assets_total_sync()
print(total.count)

by_type = metrics.get_metrics_assets_by_type_sync()
by_provider = metrics.get_metrics_assets_by_provider_sync()

top = metrics.get_metrics_top_assets_sync(
start="2025-01-01T00:00:00Z",
end="2025-02-01T00:00:00Z",
limit=10,
)
queries = metrics.get_metrics_top_queries_sync(
start="2025-01-01T00:00:00Z",
end="2025-02-01T00:00:00Z",
limit=10,
)

Owners

Search the catalog for asset owners (users and teams).

from marmot import AuthenticatedApiClient, OwnersApi

owners = OwnersApi(AuthenticatedApiClient.connect())

hits = owners.get_owners_search_sync(q="alice", limit=10)
for owner in hits.owners or []:
print(owner)

Admin

Trigger or poll a full search reindex. Requires admin permissions.

from marmot import AdminApi, AuthenticatedApiClient

admin = AdminApi(AuthenticatedApiClient.connect())

accepted = admin.post_admin_search_reindex_sync()
status = admin.get_admin_search_reindex_sync()
print(status.running, status.es_configured)

Building an agent?

Marmot for Agents builds on the SDK to give LLM agents the catalog as tools and writes their lineage automatically.

Read the guide