Skip to main content

Python SDK

The Sealmetrics Python SDK is the official, type-safe client for the Sealmetrics Analytics API, designed for data scientists and analytics professionals. It wraps the REST API with Pydantic models, native pandas/polars DataFrame conversion, magic date shortcuts, automatic pagination, and retries.

note

There is also a Node.js SDK, but it is deprecated and no longer recommended. The Python SDK is the supported path for programmatic access.

Features

  • Type-safe: full Pydantic models with IDE autocompletion
  • DataFrame integration: native pandas and polars support
  • Sync and async: both clients built on httpx
  • Magic dates: shortcuts like "30d", "mtd", "ytd"
  • Automatic pagination: fetches all results seamlessly
  • Automatic retries: exponential backoff for reliability
  • Bulk exports: download large datasets efficiently
  • Batch queries: multiple queries in a single request

Installation

The package is published as sealmetrics and requires Python 3.10+.

pip install sealmetrics

# With pandas support (recommended for data scientists)
pip install sealmetrics[pandas]

# With Jupyter support (pandas + progress bars)
pip install sealmetrics[jupyter]

# With polars support
pip install sealmetrics[polars]

# All optional dependencies
pip install sealmetrics[all]

Authentication

The SDK authenticates with a Sealmetrics API key. Generate one in Settings → API Keys (it starts with the sm_ prefix and is shown only once).

API keys are read-only: they hold the stats:read, sites:read, and accounts:read scopes, which is exactly what the analytics methods below require. Write operations (such as creating or updating segments) need a JWT session instead. See Authentication and API Tokens for details.

from sealmetrics import SealMetrics

client = SealMetrics(
api_key="sm_live_xxxxx", # Settings → API Keys
account_id="acc_123"
)

Client initialization

The main entry point is the SealMetrics class.

from sealmetrics import SealMetrics

client = SealMetrics(
api_key="sm_live_xxxxx",
account_id="acc_123",
base_url="https://api.sealmetrics.com", # Default
timeout=30.0, # Request timeout (seconds)
max_retries=3, # Retry attempts
show_progress=True, # Progress bars (requires tqdm)
)

# Use as a context manager for proper cleanup
with SealMetrics(api_key="...", account_id="...") as client:
df = client.pages("30d").to_df()
ArgumentDefaultDescription
api_keySealmetrics API key (required)
account_idAccount to query (required)
base_urlhttps://api.sealmetrics.comAPI base URL
timeout30.0Per-request timeout in seconds
max_retries3Retry attempts with exponential backoff
show_progressTrueShow pagination progress bars (if tqdm is installed)

Magic dates

Most methods accept a period as the first argument. You can use a shortcut, a Period enum, or an explicit date range.

# Relative periods
client.pages("today") # Today
client.pages("yesterday") # Yesterday
client.pages("7d") # Last 7 days
client.pages("30d") # Last 30 days
client.pages("90d") # Last 90 days
client.pages("12m") # Last 12 months

# To-date periods
client.pages("wtd") # Week to date
client.pages("mtd") # Month to date
client.pages("qtd") # Quarter to date
client.pages("ytd") # Year to date

# Named periods
client.pages("this_month")
client.pages("last_month")
client.pages("this_year")
client.pages("last_year")

# Explicit date range
client.pages("2025-01-01", "2025-01-31")

Analytics methods

Query methods return a QueryResult (see below) unless noted otherwise. They accept the period arguments plus optional filters (utm_source, utm_medium, segment, sort_by, page_size, max_pages, …).

Traffic analytics

MethodDescriptionReturns
overview()Dashboard metrics with time seriesOverviewData
pages()Page-level metricsQueryResult[PageMetrics]
landing_pages()Entry page metricsQueryResult[LandingPageMetrics]
time_series()Daily time seriesQueryResult[TimeSeriesPoint]
realtime()Real-time visitors (last 5 min)dict

Traffic sources (UTM)

MethodDescriptionReturns
sources()By utm_sourceQueryResult[SourceMetrics]
mediums()By utm_mediumQueryResult[MediumMetrics]
campaigns()By utm_campaignQueryResult[CampaignMetrics]
channels()GA4-style channelsQueryResult[ChannelMetrics]

Geographic and device

MethodDescriptionReturns
countries()By country (ISO codes)QueryResult[GeoMetrics]
devices()Device / browser / OS breakdownDeviceBreakdown

Conversions

MethodDescriptionReturns
conversions()Conversions by typeQueryResult[ConversionMetrics]
microconversions()MicroconversionsQueryResult[MicroconversionMetrics]
funnel()Funnel analysisFunnelReport

Example

from sealmetrics import CompareMode, Period, SealMetrics

client = SealMetrics(api_key="sm_live_xxxxx", account_id="acc_123")

# Dashboard overview with year-over-year comparison
overview = client.overview("30d", compare="yoy")
print(f"Pageviews: {overview.traffic.page_views}")
print(f"Bounce rate: {overview.traffic.bounce_rate}%")
if overview.comparison:
print(f"YoY change in entrances: {overview.traffic.entrances_change}%")

# Pages as a DataFrame (automatic pagination)
df_pages = client.pages("30d").to_df()
print(df_pages.head())

# Filter to paid traffic only
df_paid = client.sources("mtd", utm_medium="cpc").to_df()

# Top countries
df_countries = client.countries("30d").to_df()
top10 = df_countries.nlargest(10, "entrances")

# Device breakdown
devices = client.devices("30d")
print([(d.device_type, d.entrances) for d in devices.devices])

# Funnel analysis
funnel = client.funnel("30d")
print(f"Overall conversion rate: {funnel.overall_conversion_rate}%")

# Real-time visitors
realtime = client.realtime()
print(f"Active visitors (5 min): {realtime['active_visitors']}")

# Type-safe period and comparison enums
overview_typed = client.overview(Period.MONTH_TO_DATE, compare=CompareMode.PREVIOUS)

Generic query

For endpoints without a dedicated method, use query():

data = client.query(
"/stats/properties/breakdown",
start="30d",
property_key="plan",
)

QueryResult

Query methods return a QueryResult, which behaves like a list of typed models and adds DataFrame conversion.

result = client.pages("30d")

# Metadata
print(result.total)
print(result.has_more)

# Conversions
df = result.to_df() # pandas DataFrame
df_pl = result.to_polars() # polars DataFrame
dicts = result.to_dicts() # list of dicts

# Iterate directly
for page in result:
print(f"{page.path}: {page.page_views} views")

Segments

Segments are saved filter configurations exposed via client.segments. Reading segments works with a read-only API key; creating or modifying them requires a write-capable session (see Authentication).

# List all available segments (including built-in system segments)
for seg in client.segments.list_all():
print(f"{seg.name}: {seg.display_name}")

# list() is an alias for list_all()
custom = client.segments.list_all(include_system=False)

# Get details by ID or name slug
segment = client.segments.get("paid-spain")

# Use a segment in a query
df = client.pages("30d", segment="paid-spain").to_df()
df = client.sources("mtd", segment="paid-spain").to_df()

Segment management methods (write scope required):

# Create a segment
segment = client.segments.create(
name="paid-spain",
display_name="Paid Traffic Spain",
filters=[
{"field": "utm_medium", "operator": "eq", "value": "cpc"},
{"field": "country", "operator": "eq", "value": "ES"},
],
color="#F59E0B",
)

# Update, duplicate, delete
client.segments.update("paid-spain", display_name="Paid Traffic Spain (CPC)")
my_organic = client.segments.duplicate("organic-search", "my-organic")
client.segments.delete("paid-spain")

See Segments for the underlying REST resource.

Bulk exports

For large datasets, use client.exports instead of paginating.

# Estimate export size first
estimate = client.exports.estimate("pages", "30d")
print(f"Estimated rows: {estimate.estimated_rows}")

if estimate.can_stream:
# Stream small exports (< 10K rows) directly
df = client.exports.stream_df("pages", "30d")
else:
# Create an async export job
job = client.exports.create("pages", "30d", format="csv")

# Wait and download as a DataFrame
df = client.exports.download_df(job.job_id)

# Or download raw bytes
content = client.exports.download(job.job_id)
with open("export.csv", "wb") as f:
f.write(content)

Export types: pages, sources, mediums, campaigns, countries, devices, browsers, os, conversions, microconversions, time_series, landing_pages.

Other export helpers: estimate(), create(), get_status(), list_all(), cancel(), wait(), download(), download_df(), stream(), stream_df(). See Exports.

Batch queries

Reduce latency by fetching multiple datasets in one request via client.batch.

# Execute arbitrary batch queries
results = client.batch.execute([
{"id": "overview", "endpoint": "/stats/overview", "params": {"period": "30d"}},
{"id": "pages", "endpoint": "/stats/pages", "params": {"period": "30d", "page_size": 100}},
{"id": "sources", "endpoint": "/stats/sources", "params": {"period": "30d"}},
])
overview = results["overview"]

# Dashboard shortcut: overview + top pages + top sources + channels + devices
dashboard = client.batch.dashboard("30d", compare="previous")
print(dashboard["overview"])
print(dashboard["top_pages"])
print(dashboard["channels"])

client.batch also exposes validate() to check queries without executing them. See Batch Requests.

Async support

Use the async methods (prefixed with async_) for concurrent operations.

import asyncio
from sealmetrics import SealMetrics

async def analyze():
async with SealMetrics(api_key="...", account_id="...") as client:
overview, pages, sources = await asyncio.gather(
client.async_overview("30d"),
client.async_pages("30d"),
client.async_sources("30d"),
)
return overview, pages, sources

overview, pages, sources = asyncio.run(analyze())

Error handling

All SDK errors inherit from SealMetricsError.

from sealmetrics import (
SealMetrics,
AuthenticationError,
AuthorizationError,
RateLimitError,
ValidationError,
NotFoundError,
SealMetricsError,
)

try:
data = client.pages("30d")
except AuthenticationError:
print("Invalid or missing API key")
except AuthorizationError:
print("No access to this account")
except RateLimitError as e:
print(f"Rate limited, retry after {e.retry_after}s")
except ValidationError as e:
print(f"Invalid parameters: {e.message}")
except NotFoundError:
print("Resource not found")
except SealMetricsError as e:
print(f"API error: {e.message} (status={e.status_code}, request_id={e.request_id})")

The full exception hierarchy: SealMetricsError (base), AuthenticationError, AuthorizationError, RateLimitError (with retry_after), ValidationError, NotFoundError, ConflictError, ExportError, NetworkError, TimeoutError, and ServerError.

Pagination

Paginated query methods fetch all pages automatically by default. You can control this behavior with arguments:

# Default: fetch everything (auto-pagination)
all_pages = client.pages("30d")

# Top 100 only — a single page
top_pages = client.pages("30d", max_pages=1, page_size=100, sort_by="page_views")

The returned QueryResult exposes total and has_more so you can inspect the result set. For very large datasets, prefer bulk exports. See Pagination & Sorting for the REST-level details.

See also