# Introduction (/docs)
SeeStack is a full-stack observability platform that gives you visibility into errors, logs, HTTP traffic, user sessions, cron jobs, and feature flags — all from a single SDK. Initialize once at application startup and the SDK handles the rest: buffering, batching, retrying, and delivering telemetry to your SeeStack instance.
All SDK calls are async and fail-safe. The SDK will never crash your application or block your main thread.
What You Can Do [#what-you-can-do]
| Feature | Description |
| ------------------------------------------------- | ---------------------------------------------------------------- |
| [Error Tracking](/docs/features/error-tracking) | Capture exceptions with stack traces, user context, and metadata |
| [Log Ingestion](/docs/features/log-ingestion) | Structured logs at five severity levels with arbitrary metadata |
| [HTTP Monitoring](/docs/features/http-monitoring) | Track inbound and outbound HTTP requests with timing and status |
| [Session Replay](/docs/features/session-replay) | Stream DOM and interaction events for later playback |
| [Cron Monitoring](/docs/features/cron-monitoring) | Heartbeat pings for scheduled job success or failure |
| [Feature Flags](/docs/features/feature-flags) | Evaluate remote flags with rollout percentages and targeting |
Get Started [#get-started]
AI-Friendly Docs [#ai-friendly-docs]
Our documentation is available in machine-readable formats for AI coding assistants, harness agents, and automated pipelines.
# Errors (/docs/api/errors)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Heartbeat (/docs/api/heartbeat)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# HTTP Requests (/docs/api/http-requests)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Overview (/docs/api)
The SeeStack ingestion API accepts telemetry data over HTTP. All endpoints require the `X-SeeStack-Key` header for authentication and return `202 Accepted` on success.
Endpoints [#endpoints]
Authentication [#authentication]
All endpoints require the `X-SeeStack-Key` header with your raw API key:
```
X-SeeStack-Key: seestack_live_abc123xyz...
```
The key is validated and the associated project is resolved automatically. You do not need to send a project ID (except for the HTTP requests endpoint).
Response Format [#response-format]
Most endpoints return a standard envelope:
```json
{
"success": true,
"data": { ... },
"meta": {
"requestId": "uuid",
"timestamp": "2026-03-31T12:00:00.000Z"
}
}
```
The `/ingest/v1/http-requests` and `/ingest/v1/heartbeat` endpoints return raw JSON objects instead of the standard envelope.
# Logs (/docs/api/logs)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Session Replay (/docs/api/session-replay)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
# Buffering & Flushing (/docs/guides/buffering-and-flushing)
The SeeStack SDK buffers events in memory and sends them in the background. This ensures that capture calls (`captureError`, `captureLog`, etc.) return instantly and never block your application.
How Buffering Works [#how-buffering-works]
Each feature maintains its own in-memory **ring buffer**:
| Property | Value |
| ------------------------------ | ------------------------------------------ |
| Buffer type | Ring buffer (bounded FIFO) |
| Default size | 500 items per feature |
| Eviction policy | Oldest item dropped when full (tail-drop) |
| Features with separate buffers | Errors, Logs, HTTP Requests, Replay Events |
When the buffer is full, the oldest item is silently dropped to make room. The SDK emits a single internal warning (visible in debug mode) but does not throw an error.
Flush Triggers [#flush-triggers]
The buffer is flushed (sent to the backend) in four situations:
| Trigger | Behavior |
| ------------ | --------------------------------------------------------- |
| **Timer** | Every `flushIntervalMs` (default: 5,000ms) |
| **Capacity** | When the buffer reaches 80% full |
| **Manual** | When you call `flush()` |
| **Shutdown** | Best-effort drain on application exit (5-second deadline) |
Feature-Specific Behavior [#feature-specific-behavior]
Not all features use the same delivery strategy:
| Feature | Delivery | Batching |
| ------------------- | ------------------------------------------ | --------------------------- |
| **Errors** | Sent immediately (not buffered) | No — 1 per request |
| **Logs** | Buffered, flushed on timer | No — 1 per request |
| **HTTP Requests** | Buffered, flushed on timer or at 50 items | Yes — up to 100 per request |
| **Session Replay** | Buffered, flushed every 5s or at 50 events | Yes — unbounded batch size |
| **Cron Heartbeats** | Sent immediately after job completes | No — 1 per request |
Manual Flush [#manual-flush]
Call `flush()` to force-send all buffered events. This is useful before:
* Application shutdown or process exit
* Serverless function completion
* Test assertions that verify backend receipt
```js
// Flush and wait for completion
await SeeStack.flush();
```
```python
# Flush and wait for completion
seestack.flush()
```
```go
// Flush and wait for completion
seestack.Flush()
```
Shutdown Drain [#shutdown-drain]
On process exit, the SDK attempts to flush all remaining buffered events within a **5-second deadline**. This is best-effort — if the deadline expires, remaining events are discarded.
```js title="server.js"
// Node.js: ensure graceful shutdown
process.on('SIGTERM', async () => {
await SeeStack.flush();
process.exit(0);
});
```
```js title="browser.js"
// Browser: use sendBeacon-based flush before page unload
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
SeeStack.flush();
}
});
```
```python title="app.py"
import atexit
atexit.register(seestack.flush)
```
```go title="main.go"
// Use defer in main() to ensure flush on exit
func main() {
seestack.Init(config)
defer seestack.Flush()
// ... application logic
}
```
The SDK is fully non-blocking. All capture calls push events into the buffer and return immediately. A single background worker per feature handles flushing — no extra threads are spawned per event.
Configuring Buffer Size [#configuring-buffer-size]
You can adjust the buffer size at initialization:
```js
SeeStack.init({
apiKey: '...',
host: '...',
bufferSize: 1000, // 1,000 items per feature instead of default 500
});
```
```python
seestack.init(
api_key="...",
host="...",
buffer_size=1000,
)
```
```go
seestack.Init(seestack.Config{
APIKey: "...",
Host: "...",
BufferSize: 1000,
})
```
The total memory footprint scales with buffer size: \~500 items x 5 features = 2,500 items max at default settings, estimated at under **15 MB** of heap usage.
# Privacy & Data Masking (/docs/guides/privacy-masking)
SeeStack masks all sensitive data **client-side** before transmission. This is mandatory and automatic — sensitive data never leaves your application.
What Gets Masked Automatically [#what-gets-masked-automatically]
Session Replay [#session-replay]
| Rule | Behavior |
| ------------------------------ | ------------------------------------------------- |
| `` | Value replaced with `[MASKED]` — never captured |
| `data-seestack-mask` attribute | Entire element content masked |
| `seestack-mask` CSS class | Entire element content masked |
| Clipboard events | Never captured (paste may contain sensitive data) |
HTTP Monitoring [#http-monitoring]
| Rule | Behavior |
| --------------------------------------------------------------------- | -------------------------- |
| `Authorization` header | Stripped before recording |
| `Cookie` header | Stripped before recording |
| `X-SeeStack-Key` header | Stripped before recording |
| `X-API-Key` header | Stripped before recording |
| `X-Auth-Token` header | Stripped before recording |
| Query params: `token`, `key`, `secret`, `password`, `auth`, `api_key` | Replaced with `[FILTERED]` |
Error Tracking [#error-tracking]
| Rule | Behavior |
| --------------------------- | -------------------------------- |
| Database connection strings | Never included in error metadata |
| SQL with parameter values | Parameter values sanitized |
Log Ingestion [#log-ingestion]
| Rule | Behavior |
| --------------------------------------------------------------------------- | ---------------------------- |
| Metadata fields named `password`, `secret`, `token`, `key`, `authorization` | Values masked before sending |
Marking Elements for Masking [#marking-elements-for-masking]
Use the `data-seestack-mask` attribute or the `seestack-mask` CSS class on any HTML element to ensure its content is never captured by session replay:
```html
Credit card: 4242 **** **** 1234
Sensitive information here
```
URL Query Parameter Stripping [#url-query-parameter-stripping]
HTTP monitoring and session replay automatically strip query parameters that match sensitive patterns:
```
// Before masking
https://api.example.com/auth?token=eyJhbGc...&redirect=/dashboard
// After masking
https://api.example.com/auth?token=[FILTERED]&redirect=/dashboard
```
Parameters matching these keywords are stripped: `token`, `key`, `secret`, `password`, `auth`, `api_key`.
HTTPS Enforcement [#https-enforcement]
All SDK communication must use HTTPS. The SDK will refuse to send data over plain HTTP in production environments. TLS certificates are validated on every request.
API Key Protection [#api-key-protection]
The API key is never written to logs or console output unless `debug: true` is explicitly enabled. Even in debug mode, the key is only visible in HTTP request headers — it is never logged as a standalone value.
Best Practices [#best-practices]
1. **Use `data-seestack-mask` liberally** — when in doubt, mask it. There is no performance cost to masking.
2. **Never put PII in arbitrary metadata** fields like error or log metadata unless necessary for debugging.
3. **Review captured data** in the SeeStack dashboard periodically to ensure no sensitive information is leaking through.
4. **Use environment variables** for your API key — never hardcode it in source files that may be committed to version control.
# Retry Strategy (/docs/guides/retry-strategy)
The SeeStack SDK uses exponential backoff with jitter to retry failed requests. This ensures reliable delivery without overwhelming the backend during outages.
Backoff Schedule [#backoff-schedule]
When a request fails with a retryable error, the SDK retries with increasing delays:
| Attempt | Delay |
| ------- | -------------------------- |
| 1 | Immediate |
| 2 | 1 second + random 0–500ms |
| 3 | 2 seconds + random 0–500ms |
| 4 | 4 seconds + random 0–500ms |
| 5 | 8 seconds + random 0–500ms |
| After 5 | Discard the event |
Jitter (the random 0–500ms addition) prevents thundering herd problems when many SDK instances restart simultaneously.
What Gets Retried [#what-gets-retried]
| Response | Retried? | Reason |
| -------------------------- | -------- | -------------------------------------------- |
| `5xx` Server Error | Yes | Backend is temporarily unavailable |
| Connection timeout | Yes | Network or server issue |
| Network unreachable | Yes | Transient connectivity problem |
| `429` Too Many Requests | Yes | Rate limited — respects `Retry-After` header |
| `400` Bad Request | No | Malformed payload — retrying won't help |
| `401` Unauthorized | No | Invalid API key — see below |
| `403` Forbidden | No | Access denied — configuration error |
| `422` Unprocessable Entity | No | Validation failure — fix the payload |
Network Timeouts [#network-timeouts]
The SDK enforces strict timeouts to prevent blocking:
| Parameter | Value |
| --------------------- | --------- |
| Connection timeout | 3 seconds |
| Read/write timeout | 3 seconds |
| Total request timeout | 5 seconds |
If the backend does not respond within these limits, the request fails and enters the retry cycle.
401 Handling [#401-handling]
A `401 Unauthorized` response **disables the SDK for the entire session**. This is not a transient error — it means your API key is invalid or missing. The SDK will:
1. Log a warning: "SeeStack SDK: invalid API key — disabling SDK"
2. Stop all SDK operations (capture, flush, etc.)
3. Never retry the request
If you see this, check your `apiKey` in the init configuration.
400 Handling [#400-handling]
A `400 Bad Request` means the payload was malformed. The SDK:
1. Logs a debug message: "SeeStack SDK: malformed payload — dropping event"
2. Discards the event
3. Does not retry
This typically indicates a bug in payload construction, not a transient issue.
Your Application Is Never Affected [#your-application-is-never-affected]
The SDK fails silently in all cases. If the backend is unreachable, data is buffered locally and retried in the background. If retries are exhausted, events are discarded. Your application's performance and stability are never impacted.
All retry logic runs on a background worker. Capture calls (`captureError`, `captureLog`, etc.) always return immediately, regardless of network status.
Decision Tree [#decision-tree]
```
Send attempt fails
├── 401 Unauthorized
│ → Disable SDK for session, do NOT retry
│
├── 400 Bad Request / 422 Unprocessable
│ → Discard event, do NOT retry
│
├── 429 Too Many Requests
│ → Respect Retry-After header
│ → Retry up to 3 more attempts with backoff
│
├── 5xx Server Error
│ → Retry with exponential backoff (up to 5 attempts)
│ → Discard after 5 failures
│
└── Network timeout / connection refused
→ Retry with exponential backoff (up to 5 attempts)
→ Discard after 5 failures
```
# User Context (/docs/guides/user-context)
Setting user context lets you associate errors, logs, and other events with a specific user. Once set, user context is automatically attached to all subsequent SDK calls until cleared.
Set User Context [#set-user-context]
Call `setUser` after authentication (e.g. after login or session validation):
```js
SeeStack.setUser({
id: 'usr_1234',
email: 'user@example.com',
ip: '192.168.1.100',
});
```
```python
seestack.set_user(
id="usr_1234",
email="user@example.com",
ip="192.168.1.100",
)
```
```go
seestack.SetUser(seestack.User{
ID: "usr_1234",
Email: "user@example.com",
IP: "192.168.1.100",
})
```
User Fields [#user-fields]
All fields are optional. Include whichever fields are useful for your debugging workflow.
Clear User Context [#clear-user-context]
Call `clearUser` on logout or when the session ends:
```js
SeeStack.clearUser();
```
```python
seestack.clear_user()
```
```go
seestack.ClearUser()
```
How Context Propagates [#how-context-propagates]
Once set, user context is automatically included in:
| Feature | Where It Appears |
| --------------- | ----------------------------------- |
| Error tracking | `user` field on the error payload |
| HTTP monitoring | `userId` field on captured requests |
| Session replay | Linked to the user's session |
| Logs | Available via metadata correlation |
Example: Login / Logout Flow [#example-login--logout-flow]
```js title="auth.js"
async function onLogin(credentials) {
const user = await authenticate(credentials);
// Set user context so all subsequent events are associated
SeeStack.setUser({
id: user.id,
email: user.email,
});
}
function onLogout() {
SeeStack.clearUser();
SeeStack.flush(); // Ensure pending events with user context are sent
}
```
```python title="auth.py"
def on_login(credentials):
user = authenticate(credentials)
seestack.set_user(id=user.id, email=user.email)
def on_logout():
seestack.clear_user()
seestack.flush()
```
```go title="auth.go"
func onLogin(credentials Credentials) {
user := authenticate(credentials)
seestack.SetUser(seestack.User{ID: user.ID, Email: user.Email})
}
func onLogout() {
seestack.ClearUser()
seestack.Flush()
}
```
User context is optional on all events. If you don't set it globally, you can still pass user information per-event in the `captureError` context parameter.
Per-Event Override [#per-event-override]
You can override or supplement the global user context on individual calls:
```js
// Global context is "usr_1234", but this error is attributed to a different user
SeeStack.captureError(error, {
user: { id: 'usr_5678', email: 'other@example.com' },
});
```
```python
seestack.capture_error(error, context={
"user": {"id": "usr_5678", "email": "other@example.com"},
})
```
```go
seestack.CaptureError(err, &seestack.ErrorContext{
User: &seestack.User{ID: "usr_5678", Email: "other@example.com"},
})
```
Per-event user context takes precedence over the global context for that specific event.
# Cron Monitoring (/docs/features/cron-monitoring)
Cron monitoring tracks the health of your scheduled jobs. After each job execution, the SDK sends a heartbeat ping to SeeStack with the job's status, duration, and an optional message. If a job fails or misses its schedule, SeeStack triggers an alert.
The cron monitor must be created first in the SeeStack console under **Cron Monitors**. The SDK only sends heartbeat pings — it does not create monitors.
Basic Usage [#basic-usage]
Create a Monitor in the Console [#create-a-monitor-in-the-console]
In the SeeStack dashboard, go to **Cron Monitors** and create a new monitor with a slug (e.g. `daily-report-generator`). Set the expected schedule so SeeStack knows when to expect pings.
Instrument Your Job [#instrument-your-job]
Wrap your job execution with `startJob` and `finishJob`:
```js title="cron-job.js"
const handle = SeeStack.startJob('daily-report-generator');
try {
await generateReport();
SeeStack.finishJob(handle, 'success', 'Processed 1,842 records');
} catch (e) {
SeeStack.finishJob(handle, 'failed', e.message);
throw e; // Always rethrow — the SDK must not swallow job errors
}
```
```python title="cron_job.py"
handle = seestack.start_job("daily-report-generator")
try:
generate_report()
seestack.finish_job(handle, "success", "Processed 1,842 records")
except Exception as e:
seestack.finish_job(handle, "failed", str(e))
raise # Always re-raise
```
```go title="cron_job.go"
handle := seestack.StartJob("daily-report-generator")
err := generateReport()
if err != nil {
seestack.FinishJob(handle, "failed", err.Error())
return err // Always return the error
}
seestack.FinishJob(handle, "success", "Processed 1,842 records")
```
Parameters [#parameters]
startJob [#startjob]
Returns a `JobHandle` that tracks the start time internally.
finishJob [#finishjob]
Slug Format [#slug-format]
Monitor slugs must match the pattern `^[a-z0-9-]+$` — lowercase letters, numbers, and hyphens only.
**Valid:** `daily-report-generator`, `payment-reconciliation`, `cleanup-job-v2`
**Invalid:** `Daily_Report`, `my job`, `CLEANUP`
How It Works [#how-it-works]
1. `startJob(slug)` records the current timestamp and returns a handle
2. Your job executes normally
3. `finishJob(handle, status, message?)` computes `durationMs` from the start time
4. A heartbeat ping is sent **immediately** to the backend (no batching)
Heartbeats are sent immediately after `finishJob` is called — they are not buffered like logs or HTTP requests.
Never swallow job errors. Always rethrow or return the error after calling `finishJob`. The SDK reports the failure to SeeStack, but your application's error handling must still work normally.
Status Values [#status-values]
| Status | Meaning | SeeStack Behavior |
| --------- | ---------------------------- | ------------------------------------- |
| `success` | Job completed without errors | Recorded normally |
| `failed` | Job encountered an error | Triggers alert evaluation immediately |
Next Steps [#next-steps]
# Error Tracking (/docs/features/error-tracking)
Error tracking captures runtime exceptions in your application and sends them to SeeStack with full stack traces, user context, and custom metadata. Errors are sent immediately (not buffered) because they represent urgent events.
Manual Capture [#manual-capture]
Use `captureError` to report a caught exception:
```js
try {
await processOrder(orderId);
} catch (e) {
SeeStack.captureError(e, {
user: { id: 'usr_1234', email: 'user@example.com' },
metadata: { orderId, route: '/checkout' },
});
}
```
```python
try:
process_order(order_id)
except Exception as e:
seestack.capture_error(e, context={
"user": {"id": "usr_1234", "email": "user@example.com"},
"metadata": {"order_id": order_id, "route": "/checkout"},
})
```
```go
if err := processOrder(orderID); err != nil {
seestack.CaptureError(err, &seestack.ErrorContext{
User: &seestack.User{ID: "usr_1234", Email: "user@example.com"},
Metadata: map[string]any{"orderID": orderID, "route": "/checkout"},
})
}
```
Automatic Capture [#automatic-capture]
You can opt into global error handler integration to catch unhandled exceptions automatically.
**Browser:**
```js
// Captures unhandled exceptions and promise rejections
window.addEventListener('error', (event) => {
SeeStack.captureError(event.error);
});
window.addEventListener('unhandledrejection', (event) => {
SeeStack.captureError(event.reason);
});
```
**Node.js:**
```js
process.on('uncaughtException', (err) => {
SeeStack.captureError(err);
});
process.on('unhandledRejection', (reason) => {
SeeStack.captureError(reason);
});
```
```python
import sys
original_hook = sys.excepthook
def seestack_excepthook(exc_type, exc_value, exc_tb):
seestack.capture_error(exc_value)
original_hook(exc_type, exc_value, exc_tb)
sys.excepthook = seestack_excepthook
```
```go
// Go does not have global exception handlers.
// Use defer/recover in goroutines to capture panics:
func safeGo(fn func()) {
go func() {
defer func() {
if r := recover(); r != nil {
seestack.CaptureError(fmt.Errorf("panic: %v", r), nil)
}
}()
fn()
}()
}
```
Context Object [#context-object]
The optional context parameter lets you attach additional information to an error.
Error Levels [#error-levels]
| Level | Use Case |
| ------- | -------------------------------------------------- |
| `debug` | Low-priority issues for investigation |
| `info` | Informational errors that are expected |
| `warn` | Potential problems that may need attention |
| `error` | Application errors that need to be fixed (default) |
| `fatal` | Critical errors that crash the application |
How It Works [#how-it-works]
When you call `captureError`, the SDK:
1. Extracts the exception class name and message
2. Captures the stack trace as an array of frame strings
3. Attaches user context (if set globally via `setUser` or passed in the context)
4. Attaches `environment` and `release` from the init config
5. Sends the payload immediately to the backend
Errors are deduplicated server-side by exception class and stack trace. You do not need to implement any deduplication logic — send every occurrence and the backend handles grouping.
If the backend returns a `401 Unauthorized` response, the SDK disables itself for the remainder of the session. This indicates an invalid API key — check your configuration.
# Feature Flags (/docs/features/feature-flags)
Feature flag evaluation requires a **server-side SDK**. The evaluation API uses management credentials that must not be exposed in browsers or mobile apps. To use flags client-side, call your own backend endpoint which evaluates flags securely on the server and returns the results.
Feature flags let you control feature rollouts, A/B tests, and kill switches remotely without deploying code. The SDK evaluates flags against the SeeStack backend and caches results locally for performance.
Evaluate a Single Flag [#evaluate-a-single-flag]
```js
const flag = await SeeStack.getFlag('new-checkout-flow', 'usr_1234', {
plan: 'pro',
country: 'SA',
});
if (flag.enabled) {
renderNewCheckout(flag.value); // e.g. "variant-b"
} else {
renderLegacyCheckout();
}
```
```python
flag = seestack.get_flag("new-checkout-flow", user_id="usr_1234", attributes={
"plan": "pro",
"country": "SA",
})
if flag.enabled:
render_new_checkout(flag.value) # e.g. "variant-b"
else:
render_legacy_checkout()
```
```go
flag, err := seestack.GetFlag("new-checkout-flow", "usr_1234", map[string]any{
"plan": "pro",
"country": "SA",
})
if flag.Enabled {
renderNewCheckout(flag.Value) // e.g. "variant-b"
} else {
renderLegacyCheckout()
}
```
Evaluate All Flags [#evaluate-all-flags]
Fetch all flag values for a project in a single call:
```js
const flags = await SeeStack.getAllFlags('usr_1234', { plan: 'pro' });
if (flags['dark-mode']?.enabled) {
enableDarkMode();
}
```
```python
flags = seestack.get_all_flags(user_id="usr_1234", attributes={"plan": "pro"})
if flags.get("dark-mode", {}).get("enabled"):
enable_dark_mode()
```
```go
flags, err := seestack.GetAllFlags("usr_1234", map[string]any{"plan": "pro"})
if flag, ok := flags["dark-mode"]; ok && flag.Enabled {
enableDarkMode()
}
```
Flag Result [#flag-result]
Flag Types [#flag-types]
All flag values are returned as strings. Cast them to the appropriate type in your code:
| Type | Example `value` | How to Use |
| --------- | --------------------- | ---------------- |
| `boolean` | `"true"` or `"false"` | Parse as boolean |
| `string` | `"variant-b"` | Use directly |
| `number` | `"42"` | Parse as number |
Parameters [#parameters]
getFlag [#getflag]
getAllFlags [#getallflags]
Returns a `Record` mapping flag keys to their evaluation results.
Next Steps [#next-steps]
Returns a `Record` mapping flag keys to their evaluation results.
Caching [#caching]
The SDK caches flag evaluation results to minimize network calls:
| Scenario | Behavior |
| ---------------------- | ------------------------------------------------- |
| Cache hit (within 60s) | Returns cached value immediately |
| Cache miss | Fetches from backend, caches result |
| Network failure | Returns last cached value (stale-on-error) |
| First call, no cache | Returns the default value from flag configuration |
The cache TTL is **60 seconds** by default. After expiry, the next evaluation triggers a fresh fetch from the backend.
Rollout percentages are computed server-side based on `userId`. You do not need to implement any rollout logic in your code.
# HTTP Monitoring (/docs/features/http-monitoring)
HTTP monitoring captures every HTTP request your application makes or receives, recording method, host, path, status code, duration, and payload sizes. Requests are batched (up to 100 per call) for efficient delivery.
Auto-Instrumentation [#auto-instrumentation]
The simplest way to start is to enable automatic instrumentation. This patches your runtime's HTTP client to capture all requests without code changes.
```js title="app.js"
import SeeStack from '@seestack/sdk';
SeeStack.init({ apiKey: '...', host: '...' });
SeeStack.instrumentHttp();
// All fetch() and XMLHttpRequest calls are now tracked
```
**What gets patched:**
* `fetch()` (browser and Node.js)
* `XMLHttpRequest` (browser)
* `http.request` / `https.request` (Node.js)
```python title="app.py"
import seestack
seestack.init(api_key="...", host="...")
seestack.instrument_http()
# All requests via urllib3, httpx, and aiohttp are now tracked
```
**What gets patched:**
* `urllib3` (used by `requests`)
* `httpx`
* `aiohttp`
```go title="main.go"
// Use the SeeStack transport wrapper for http.Client
client := &http.Client{
Transport: seestack.NewHTTPTransport(http.DefaultTransport),
}
resp, err := client.Get("https://api.example.com/data")
```
Go does not support monkey-patching. Use the transport wrapper on HTTP clients you want to monitor.
Request Direction [#request-direction]
Each captured request is tagged with a direction:
| Direction | Meaning | Example |
| ---------- | --------------------------------------- | ------------------------------------------- |
| `inbound` | A request your application **received** | An incoming API call to your server |
| `outbound` | A request your application **made** | A call to a third-party API or microservice |
Auto-instrumentation captures **outbound** requests by default. For **inbound** request tracking, use middleware in your web framework.
Captured Data [#captured-data]
Batching [#batching]
HTTP requests are batched for efficiency:
* Up to **100 items** per batch request
* Flushed every **5 seconds** or when the batch reaches **50 items** (whichever comes first)
* Requests are buffered in a ring buffer (default: 500 items)
Query parameters are automatically stripped from captured paths to prevent leaking secrets (tokens, API keys, etc.). Only the path portion of the URL is recorded.
Sensitive headers (`Authorization`, `Cookie`, `X-SeeStack-Key`) are never captured. See [Privacy & Data Masking](/docs/guides/privacy-masking) for the full list.
Inbound Request Middleware [#inbound-request-middleware]
For server-side SDKs, use middleware to track inbound requests:
```js title="Express middleware"
import express from 'express';
const app = express();
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
SeeStack.captureHttpRequest({
traceId: req.headers['x-request-id'] || crypto.randomUUID(),
direction: 'inbound',
method: req.method,
host: req.hostname,
path: req.path,
statusCode: res.statusCode,
durationMs: Date.now() - start,
requestSize: Number(req.headers['content-length'] || 0),
responseSize: Number(res.getHeader('content-length') || 0),
timestamp: new Date(start).toISOString(),
});
});
next();
});
```
```python title="Django middleware"
import time
import uuid
from seestack import capture_http_request
class SeeStackMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
start = time.time()
response = self.get_response(request)
duration_ms = int((time.time() - start) * 1000)
capture_http_request(
trace_id=request.headers.get("X-Request-ID", str(uuid.uuid4())),
direction="inbound",
method=request.method,
host=request.get_host(),
path=request.path,
status_code=response.status_code,
duration_ms=duration_ms,
request_size=len(request.body),
response_size=len(response.content),
timestamp=start,
)
return response
```
```go title="net/http middleware"
func SeeStackMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
rw := &responseWriter{ResponseWriter: w}
next.ServeHTTP(rw, r)
seestack.CaptureHTTPRequest(seestack.HTTPRequest{
TraceID: r.Header.Get("X-Request-ID"),
Direction: "inbound",
Method: r.Method,
Host: r.Host,
Path: r.URL.Path,
StatusCode: rw.statusCode,
DurationMs: int(time.Since(start).Milliseconds()),
RequestSize: int(r.ContentLength),
ResponseSize: rw.bytesWritten,
Timestamp: start.UTC().Format(time.RFC3339),
})
})
}
```
# Log Ingestion (/docs/features/log-ingestion)
Log ingestion sends structured log entries from your application to SeeStack. Logs are buffered in memory and flushed periodically — they are not sent synchronously on every call.
Basic Usage [#basic-usage]
```js
SeeStack.captureLog('info', 'User signed in', {
userId: 'usr_1234',
method: 'oauth',
});
SeeStack.captureLog('error', 'Payment failed after 3 retries', {
orderId: 'ORD-5512',
amount: 99.90,
});
```
```python
seestack.capture_log("info", "User signed in", metadata={
"user_id": "usr_1234",
"method": "oauth",
})
seestack.capture_log("error", "Payment failed after 3 retries", metadata={
"order_id": "ORD-5512",
"amount": 99.90,
})
```
```go
seestack.CaptureLog("info", "User signed in", map[string]any{
"userID": "usr_1234",
"method": "oauth",
})
seestack.CaptureLog("error", "Payment failed after 3 retries", map[string]any{
"orderID": "ORD-5512",
"amount": 99.90,
})
```
Severity Levels [#severity-levels]
| Level | Use Case |
| ------- | --------------------------------------------------------------- |
| `debug` | Verbose output for development and debugging |
| `info` | Normal operational events (user actions, successful operations) |
| `warn` | Something unexpected that is not yet an error |
| `error` | A failure that needs investigation |
| `fatal` | A critical failure that may crash the application |
Use `warn` (not `warning`) for log severity. The `warning` alias is only accepted in error tracking, not in logs.
Parameters [#parameters]
Service and Trace Context [#service-and-trace-context]
You can attach a service name and distributed trace ID to logs for correlation:
```js
SeeStack.captureLog('warn', 'Payment retry attempt 3 of 5', {
service: 'payment-service',
traceId: 'trace-b9f2a1c3',
orderId: 'ORD-5512',
amount: 99.90,
currency: 'SAR',
});
```
```python
seestack.capture_log("warn", "Payment retry attempt 3 of 5", metadata={
"service": "payment-service",
"trace_id": "trace-b9f2a1c3",
"order_id": "ORD-5512",
"amount": 99.90,
"currency": "SAR",
})
```
```go
seestack.CaptureLog("warn", "Payment retry attempt 3 of 5", map[string]any{
"service": "payment-service",
"traceID": "trace-b9f2a1c3",
"orderID": "ORD-5512",
"amount": 99.90,
"currency": "SAR",
})
```
Buffering Behavior [#buffering-behavior]
Logs are not sent immediately. They are held in an in-memory buffer (default: 500 items) and flushed:
* Every `flushIntervalMs` (default: 5 seconds)
* When the buffer reaches 80% capacity
* On explicit `flush()` call
* On application shutdown (best-effort)
Each log is sent as an individual request — there is no batch endpoint for logs.
Need to guarantee delivery before a process exits? Call `SeeStack.flush()` (or `seestack.flush()` / `seestack.Flush()`) and await its completion. See [Buffering & Flushing](/docs/guides/buffering-and-flushing) for details.
# Session Replay (/docs/features/session-replay)
Session replay is a **browser SDK only** feature. It is not available in Node.js, Python, or Go server-side SDKs.
Session replay records DOM mutations, user interactions, and navigation events in the browser. These events are streamed to SeeStack and can be replayed in the dashboard to see exactly what users experienced.
Getting Started [#getting-started]
Generate a Session ID [#generate-a-session-id]
Create a unique session identifier when a user session begins (e.g. on page load or after login):
```js
const sessionId = crypto.randomUUID();
```
Start Recording [#start-recording]
Begin capturing events. The SDK will buffer events and flush them every 5 seconds or at 50 events.
```js
SeeStack.startReplay(sessionId);
```
You can optionally pass a custom fingerprint as the second argument. If omitted, `sessionId` is used as the fingerprint.
```js
SeeStack.startReplay(sessionId, customFingerprint);
```
Stop Recording [#stop-recording]
Stop capturing when the session ends (e.g. on logout or page unload):
```js
SeeStack.stopReplay();
```
Full Example [#full-example]
```js title="session-replay.js"
import SeeStack from '@seestack/sdk';
SeeStack.init({
apiKey: 'seestack_live_your_key_here',
host: 'https://your-seestack-instance.com',
});
// Start recording when the app loads
const sessionId = crypto.randomUUID();
SeeStack.startReplay(sessionId);
// Stop recording when the user leaves
window.addEventListener('beforeunload', () => {
SeeStack.stopReplay();
SeeStack.flush();
});
```
Captured Event Types [#captured-event-types]
| Event Type | Description |
| ------------------- | ----------------------------------------------------------- |
| `dom_mutation` | DOM tree changes (added/removed/modified elements) |
| `mouse_click` | User clicks with coordinates and target element |
| `mouse_move` | Cursor movement tracking |
| `scroll` | Page or element scroll events |
| `input` | Form field interactions (**values are masked** — see below) |
| `navigation` | URL or route changes |
| `network_request` | XHR/fetch calls (sensitive headers stripped) |
| `console` | Console log, warn, and error output |
| `error` | JavaScript error events |
| `visibility_change` | Tab focus and blur events |
| `resize` | Viewport resize events |
Privacy Masking [#privacy-masking]
Privacy masking is **mandatory**. All sensitive data is masked client-side before it leaves the browser. This is not optional.
The SDK automatically applies these masking rules:
Input Fields [#input-fields]
* `` values are **never captured** — replaced with `[MASKED]`
* Elements with the `data-seestack-mask` attribute are fully masked
* Elements with the CSS class `seestack-mask` are fully masked
```html
Sensitive content here
```
Network Events [#network-events]
These headers are stripped from captured request metadata:
* `Authorization`
* `Cookie`
* `X-SeeStack-Key`
* `X-API-Key`
* `X-Auth-Token`
URLs [#urls]
Query parameters matching these patterns are stripped: `token`, `key`, `secret`, `password`, `auth`, `api_key`.
Clipboard [#clipboard]
Clipboard events (paste operations) are never captured, as they may contain sensitive data.
Linking Errors to Sessions [#linking-errors-to-sessions]
When session replay is active and an error is captured via `captureError`, the current `sessionId` is automatically attached to the error. This lets you replay the user's session directly from the error detail view in the dashboard.
Buffering [#buffering]
Replay events are flushed:
* Every **5 seconds**
* When the buffer reaches **50 events**
Events within each batch are automatically sorted by timestamp before sending.
Next Steps [#next-steps]
# Configuration (/docs/introduction/configuration)
The SDK is configured through a single `init` call at application startup. This page documents every available option.
Full Example [#full-example]
```js title="seestack.config.js"
import SeeStack from '@seestack/sdk';
SeeStack.init({
apiKey: 'seestack_live_your_key_here',
host: 'https://your-seestack-instance.com',
environment: 'production',
release: 'v2.3.1',
flushIntervalMs: 5000,
bufferSize: 500,
debug: false,
});
```
```python title="seestack_config.py"
import seestack
seestack.init(
api_key="seestack_live_your_key_here",
host="https://your-seestack-instance.com",
environment="production",
release="v2.3.1",
flush_interval_ms=5000,
buffer_size=500,
debug=False,
)
```
```go title="config.go"
seestack.Init(seestack.Config{
APIKey: "seestack_live_your_key_here",
Host: "https://your-seestack-instance.com",
Environment: "production",
Release: "v2.3.1",
FlushIntervalMs: 5000,
BufferSize: 500,
Debug: false,
})
```
Options Reference [#options-reference]
`apiKey` and `host` are required. The SDK will not initialize without them and all methods will be no-ops.
Environment-Specific Configuration [#environment-specific-configuration]
A common pattern is to vary configuration by environment:
```js title="init.js"
SeeStack.init({
apiKey: process.env.SEESTACK_API_KEY,
host: process.env.SEESTACK_HOST,
environment: process.env.NODE_ENV,
release: process.env.APP_VERSION,
debug: process.env.NODE_ENV !== 'production',
});
```
```python title="init.py"
import os
import seestack
seestack.init(
api_key=os.environ["SEESTACK_API_KEY"],
host=os.environ["SEESTACK_HOST"],
environment=os.environ.get("APP_ENV", "development"),
release=os.environ.get("APP_VERSION"),
debug=os.environ.get("APP_ENV") != "production",
)
```
```go title="init.go"
seestack.Init(seestack.Config{
APIKey: os.Getenv("SEESTACK_API_KEY"),
Host: os.Getenv("SEESTACK_HOST"),
Environment: os.Getenv("APP_ENV"),
Release: os.Getenv("APP_VERSION"),
Debug: os.Getenv("APP_ENV") != "production",
})
```
All SDK requests must use HTTPS. The SDK will refuse to send data over plain HTTP in production environments.
Initialization Behavior [#initialization-behavior]
* Calling `init()` more than once is idempotent — subsequent calls emit a warning and are ignored.
* If `init()` has not been called, all SDK methods (`captureError`, `captureLog`, etc.) are silent no-ops.
* The SDK validates that `apiKey` is non-empty at init time and fails fast if missing.
# Installation (/docs/introduction/installation)
Prerequisites [#prerequisites]
* Node.js 18 or later (for server-side usage)
* Any modern browser (for client-side usage)
* Python 3.8 or later
* pip or poetry package manager
* Go 1.21 or later
* Go modules enabled
Install the SDK [#install-the-sdk]
```bash
npm install @seestack/sdk
```
```bash
yarn add @seestack/sdk
```
```bash
pnpm add @seestack/sdk
```
```bash
bun add @seestack/sdk
```
```bash
pip install seestack-sdk
```
```bash
go get github.com/seestack/seestack-go
```
CDN (Browser Only) [#cdn-browser-only]
For browser-only usage without a build step, you can load the SDK via a script tag:
```html
```
This exposes a global `SeeStack` object you can use directly.
Verify Installation [#verify-installation]
```js title="verify.js"
import SeeStack from '@seestack/sdk';
SeeStack.init({
apiKey: 'seestack_live_your_key_here',
host: 'https://your-seestack-instance.com',
debug: true,
});
SeeStack.captureLog('info', 'SeeStack SDK installed successfully');
```
```python title="verify.py"
import seestack
seestack.init(
api_key="seestack_live_your_key_here",
host="https://your-seestack-instance.com",
debug=True,
)
seestack.capture_log("info", "SeeStack SDK installed successfully")
```
```go title="verify.go"
package main
import "github.com/seestack/seestack-go"
func main() {
seestack.Init(seestack.Config{
APIKey: "seestack_live_your_key_here",
Host: "https://your-seestack-instance.com",
Debug: true,
})
defer seestack.Flush()
seestack.CaptureLog("info", "SeeStack SDK installed successfully", nil)
}
```
If `debug: true` is set, you should see outgoing payloads logged to your console or stderr.
Next Steps [#next-steps]
# Quick Start (/docs/introduction/quick-start)
Follow these four steps to start capturing telemetry from your application.
Install the SDK [#install-the-sdk]
Follow the [Installation](/docs/introduction/installation) guide for your language and package manager.
Initialize SeeStack [#initialize-seestack]
Call `init` once at application startup with your API key and host URL. You can find your API key in the SeeStack console under **Project Settings > API Keys**.
```js title="app.js"
import SeeStack from '@seestack/sdk';
SeeStack.init({
apiKey: 'seestack_live_your_key_here',
host: 'https://your-seestack-instance.com',
environment: 'production',
release: 'v1.0.0',
});
```
```python title="app.py"
import seestack
seestack.init(
api_key="seestack_live_your_key_here",
host="https://your-seestack-instance.com",
environment="production",
release="v1.0.0",
)
```
```go title="main.go"
package main
import "github.com/seestack/seestack-go"
func main() {
seestack.Init(seestack.Config{
APIKey: "seestack_live_your_key_here",
Host: "https://your-seestack-instance.com",
Environment: "production",
Release: "v1.0.0",
})
defer seestack.Flush()
}
```
Set `debug: true` during development to see all outgoing payloads logged to your console.
Capture Your First Error [#capture-your-first-error]
Trigger a test error to verify data flows end-to-end.
```js
try {
throw new Error('Test error from SeeStack SDK');
} catch (e) {
SeeStack.captureError(e);
}
```
```python
try:
raise ValueError("Test error from SeeStack SDK")
except Exception as e:
seestack.capture_error(e)
```
```go
import "errors"
err := errors.New("Test error from SeeStack SDK")
seestack.CaptureError(err, nil)
```
Verify in the Dashboard [#verify-in-the-dashboard]
Open the SeeStack console and navigate to **Errors**. You should see your test error appear within a few seconds. If `debug` mode is enabled, you will also see the HTTP request and response logged in your terminal.
What's Next? [#whats-next]
# Configuration Options (/docs/reference/configuration-options)
This page provides a complete reference for every option accepted by `SeeStack.init()`.
All Options [#all-options]
Required vs Optional [#required-vs-optional]
`apiKey` and `host` are the only required options. Without them, the SDK will not initialize and all methods will be no-ops. The SDK validates that `apiKey` is non-empty at init time.
Default Values Summary [#default-values-summary]
| Option | Default |
| ----------------- | ----------------------- |
| `environment` | Not set |
| `release` | Not set |
| `flushIntervalMs` | `5000` (5 seconds) |
| `bufferSize` | `500` items per feature |
| `debug` | `false` |
Memory Impact [#memory-impact]
The buffer size directly affects SDK memory usage:
| Buffer Size | Items (5 features) | Estimated Heap |
| ------------- | ------------------ | -------------- |
| 100 | 500 total | \~3 MB |
| 500 (default) | 2,500 total | \~15 MB |
| 1000 | 5,000 total | \~30 MB |
Each item is estimated at 1–5 KB depending on the event type and metadata size.
Initialization Behavior [#initialization-behavior]
| Scenario | Behavior |
| --------------------------------- | ------------------------------------------ |
| First `init()` call | SDK initializes normally |
| Second `init()` call | Warning emitted, call ignored (idempotent) |
| SDK method called before `init()` | Silent no-op — no error thrown |
| Empty `apiKey` passed | SDK fails fast with an error at init time |
| HTTP `host` in production | SDK refuses to send data (HTTPS required) |
Environment-Specific Recommendations [#environment-specific-recommendations]
Development [#development]
```json
{
"debug": true,
"environment": "development"
}
```
Staging [#staging]
```json
{
"debug": false,
"environment": "staging",
"flushIntervalMs": 3000
}
```
Production [#production]
```json
{
"debug": false,
"environment": "production",
"flushIntervalMs": 5000,
"bufferSize": 500
}
```
# Data Models (/docs/reference/data-models)
This page documents all data types and structures you interact with when using the SeeStack SDK.
The configuration object passed to `init()`.
See [Configuration Options](/docs/reference/configuration-options) for detailed descriptions.
User identity attached to events. All fields are optional.
Set globally with `setUser()` or per-event via the context parameter.
Optional context passed as the second argument to `captureError`.
Data captured for each HTTP request by `instrumentHttp()` or manual instrumentation.
A single event captured during session replay.
Opaque handle returned by `startJob()` and passed to `finishJob()`.
The handle internally tracks:
* The monitor `slug`
* The job start timestamp (used to compute `durationMs`)
You do not need to inspect or modify the handle — just pass it through.
Data sent to SeeStack when `finishJob()` is called.
Result of evaluating a single feature flag via `getFlag()`.
Result of evaluating all flags via `getAllFlags()`.
```ts
type FlagsMap = Record
```
A mapping of flag keys to their evaluation results. Example:
```json
{
"new-checkout-flow": { "enabled": true, "value": "variant-b" },
"dark-mode": { "enabled": false, "value": "false" }
}
```
# SDK API Reference (/docs/reference/sdk-api)
This page documents every public method in the SeeStack SDK.
init [#init]
Initialize the SDK. Must be called once at application startup before any other SDK method.
```js
SeeStack.init({
apiKey: 'seestack_live_...',
host: 'https://your-seestack-instance.com',
environment: 'production',
release: 'v1.0.0',
debug: false,
});
```
```python
seestack.init(
api_key="seestack_live_...",
host="https://your-seestack-instance.com",
environment="production",
release="v1.0.0",
debug=False,
)
```
```go
seestack.Init(seestack.Config{
APIKey: "seestack_live_...",
Host: "https://your-seestack-instance.com",
Environment: "production",
Release: "v1.0.0",
Debug: false,
})
```
Calling `init` more than once is idempotent — subsequent calls emit a warning and are ignored. If `init` has not been called, all other SDK methods are silent no-ops.
***
captureError [#captureerror]
Capture an exception and send it to SeeStack immediately.
```js
SeeStack.captureError(error, {
level: 'error',
user: { id: 'usr_1234' },
metadata: { route: '/checkout' },
});
```
```python
seestack.capture_error(error, context={
"level": "error",
"user": {"id": "usr_1234"},
"metadata": {"route": "/checkout"},
})
```
```go
seestack.CaptureError(err, &seestack.ErrorContext{
Level: "error",
User: &seestack.User{ID: "usr_1234"},
Metadata: map[string]any{"route": "/checkout"},
})
```
Errors are sent immediately (not buffered). See [Error Tracking](/docs/features/error-tracking).
***
captureLog [#capturelog]
Capture a structured log entry. Logs are buffered and flushed periodically.
```js
SeeStack.captureLog('info', 'Order processed', { orderId: 'ORD-123' });
```
```python
seestack.capture_log("info", "Order processed", metadata={"order_id": "ORD-123"})
```
```go
seestack.CaptureLog("info", "Order processed", map[string]any{"orderID": "ORD-123"})
```
See [Log Ingestion](/docs/features/log-ingestion).
***
flush [#flush]
Force-flush all buffered events to the backend. Returns a promise that resolves when all events have been sent (or the 5-second deadline expires).
```js
await SeeStack.flush();
```
```python
seestack.flush()
```
```go
seestack.Flush()
```
See [Buffering & Flushing](/docs/guides/buffering-and-flushing).
***
setUser [#setuser]
Set the default user context for all subsequent SDK events.
```js
SeeStack.setUser({ id: 'usr_1234', email: 'user@example.com' });
```
```python
seestack.set_user(id="usr_1234", email="user@example.com")
```
```go
seestack.SetUser(seestack.User{ID: "usr_1234", Email: "user@example.com"})
```
See [User Context](/docs/guides/user-context).
***
clearUser [#clearuser]
Clear the previously set user context.
```js
SeeStack.clearUser();
```
```python
seestack.clear_user()
```
```go
seestack.ClearUser()
```
***
instrumentHttp [#instrumenthttp]
Enable automatic HTTP request instrumentation. Patches the runtime's native HTTP client to capture all outbound requests.
```js
SeeStack.instrumentHttp();
// Patches: fetch, XMLHttpRequest, http.request, https.request
```
```python
seestack.instrument_http()
# Patches: urllib3, httpx, aiohttp
```
```go
// Go: use the transport wrapper instead
client := &http.Client{
Transport: seestack.NewHTTPTransport(http.DefaultTransport),
}
```
See [HTTP Monitoring](/docs/features/http-monitoring).
***
captureHttpRequest [#capturehttprequest]
Manually capture an HTTP request event. Use this for inbound requests (via middleware) or when auto-instrumentation is not suitable.
```js
SeeStack.captureHttpRequest({
traceId: 'trace-001',
direction: 'inbound',
method: 'POST',
host: 'api.myapp.com',
path: '/v1/orders',
statusCode: 201,
durationMs: 45,
requestSize: 512,
responseSize: 128,
timestamp: new Date().toISOString(),
});
```
```python
seestack.capture_http_request(
trace_id="trace-001",
direction="inbound",
method="POST",
host="api.myapp.com",
path="/v1/orders",
status_code=201,
duration_ms=45,
request_size=512,
response_size=128,
timestamp=datetime.utcnow().isoformat() + "Z",
)
```
```go
seestack.CaptureHTTPRequest(seestack.HTTPRequest{
TraceID: "trace-001",
Direction: "inbound",
Method: "POST",
Host: "api.myapp.com",
Path: "/v1/orders",
StatusCode: 201,
DurationMs: 45,
RequestSize: 512,
ResponseSize: 128,
Timestamp: time.Now().UTC().Format(time.RFC3339),
})
```
Captured requests are batched (up to 100) and flushed periodically. See [HTTP Monitoring](/docs/features/http-monitoring).
***
startReplay [#startreplay]
Start recording session replay events. **Browser SDK only.**
```js
SeeStack.startReplay(sessionId);
SeeStack.startReplay(sessionId, customFingerprint);
```
See [Session Replay](/docs/features/session-replay).
***
stopReplay [#stopreplay]
Stop recording session replay events. **Browser SDK only.**
```js
SeeStack.stopReplay();
```
***
startJob [#startjob]
Begin monitoring a cron job execution. Returns a handle for use with `finishJob`.
```js
const handle = SeeStack.startJob('daily-report-generator');
```
```python
handle = seestack.start_job("daily-report-generator")
```
```go
handle := seestack.StartJob("daily-report-generator")
```
Returns a `JobHandle` that tracks the start timestamp.
***
finishJob [#finishjob]
Complete a cron job and send a heartbeat ping to SeeStack. Sent immediately (not buffered).
```js
SeeStack.finishJob(handle, 'success', 'Processed 1,842 records');
```
```python
seestack.finish_job(handle, "success", "Processed 1,842 records")
```
```go
seestack.FinishJob(handle, "success", "Processed 1,842 records")
```
See [Cron Monitoring](/docs/features/cron-monitoring).
***
getFlag [#getflag]
Evaluate a single feature flag. **Server-side SDK only.**
```js
const flag = await SeeStack.getFlag('new-checkout-flow', 'usr_1234', {
plan: 'pro',
});
// flag.enabled: boolean, flag.value: string
```
```python
flag = seestack.get_flag("new-checkout-flow", user_id="usr_1234", attributes={"plan": "pro"})
# flag.enabled: bool, flag.value: str
```
```go
flag, err := seestack.GetFlag("new-checkout-flow", "usr_1234", map[string]any{"plan": "pro"})
// flag.Enabled: bool, flag.Value: string
```
Returns a `FlagResult` with `enabled` (boolean) and `value` (string). Results are cached for 60 seconds.
See [Feature Flags](/docs/features/feature-flags).
***
getAllFlags [#getallflags]
Evaluate all feature flags for a project in a single call. **Server-side SDK only.**
```js
const flags = await SeeStack.getAllFlags('usr_1234', { plan: 'pro' });
// flags['new-checkout-flow'].enabled
```
```python
flags = seestack.get_all_flags(user_id="usr_1234", attributes={"plan": "pro"})
```
```go
flags, err := seestack.GetAllFlags("usr_1234", map[string]any{"plan": "pro"})
```
Returns `Record` mapping flag keys to evaluation results.