Automatic HTTP request monitoring for Python applications
Project description
API Monitor SDK — Python
Zero-code HTTP monitoring and real-time API validation for Python web applications.
Works with Django, Flask, FastAPI, and any code that usesrequests,httpx, orurllib3— no code changes required.
Table of Contents
- Overview
- Installation
- Quick Start
- How It Works — Two Flows
- Framework Integration
- Configuration Reference
- Features In Depth
- Graceful Shutdown
- Security Notes
- Troubleshooting
- Changelog
Overview
The API Monitor SDK automatically intercepts every inbound and outbound HTTP request made by your application and sends rich telemetry — latency, status codes, headers, request/response bodies, caller identity — to the API Monitor platform.
| Capability | Details |
|---|---|
| Zero-code instrumentation | One init() call at startup, no decorators or middleware to add |
| Framework auto-detection | Django, Flask, FastAPI / Starlette patched automatically |
| Library interception | requests, httpx, urllib3 patched transparently |
| Validation gate | Block / rate-limit requests based on platform rules, <2 ms overhead |
| Async batching | Metrics sent in background — zero latency impact on your API |
| Offline mode | Metrics cached in local SQLite when the platform is unreachable |
| PII scrubbing | Emails, credit cards, phone numbers, API keys redacted before send |
| Identity resolution | Caller identified via JWT, headers, body fields, or IP hash |
| Live config reload | Sampling rate, batch size, exclusions updated from the platform without restart |
| Fork safe | Re-initialises cleanly after os.fork() (Gunicorn, uWSGI) |
Installation
pip install api-monitor-sdk
Python 3.8+ required. No other mandatory dependencies — requests is already installed in almost every Python project.
Quick Start
Ingest-only (default — metrics, no request blocking)
import apimonitor
apimonitor.init(api_key="your-api-key")
That is the entire integration. Every HTTP request your server handles is now captured and streamed to the platform.
With request validation (rate-limiting / blocking)
import apimonitor
apimonitor.init(
api_key="your-api-key",
enable_validation=True, # enable the sync validation gate
)
When validation is enabled, a single /validate call to the platform acts as both the gate decision and the metric record — no extra network round-trip.
Both disabled (dry-run / testing)
apimonitor.init(
api_key="your-api-key",
enable_ingest=False,
enable_validation=False,
)
How It Works — Two Flows
Incoming request
│
▼
┌─────────────────────────────────────────┐
│ Flow 1 — Validation Gate (optional) │
│ │
│ 1. Check in-process LRU cache │ ← sub-ms, no network
│ 2. Check circuit breaker state │ ← sub-ms, no network
│ 3. POST /validate ──────────────────┼──► Platform decides allow/deny
│ (with metric piggybacked) │ AND saves metric in one call
│ │
│ If denied → return 403/429 immediately │
└─────────────────────────────────────────┘
│ (allowed)
▼
Your application handles the request
│
▼
┌─────────────────────────────────────────┐
│ Flow 2 — Async Metric Ingest │
│ (only when enable_validation=False) │
│ │
│ Metric queued in-memory │
│ Background thread batches & sends │
│ → POST /ingest (batch) │
│ On failure → saved to SQLite offline │
└─────────────────────────────────────────┘
Key efficiency principle: When
enable_validation=True, the metric is embedded inside the/validatepayload. The platform processes both the allow/deny decision and the metric storage in a single HTTP call./ingestis never called separately.
Framework Integration
Django
Add the init() call at the top of your settings.py or in your AppConfig.ready() method.
settings.py
import apimonitor
apimonitor.init(
api_key="your-api-key",
framework="django",
environment="production",
exclude=["/health/", "/metrics/"],
)
apps.py (recommended for Django apps)
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = "myapp"
def ready(self):
import apimonitor
apimonitor.init(
api_key="your-api-key",
framework="django",
environment="production",
)
The SDK patches django.core.handlers.base.BaseHandler.get_response — every request/response cycle is captured with zero changes to your views.
Flask
Call init() before app.run() or before WSGI server startup.
from flask import Flask
import apimonitor
app = Flask(__name__)
apimonitor.init(
api_key="your-api-key",
framework="flask",
environment="production",
sampling_rate=0.5, # capture 50% of requests
exclude=["/healthz", "/ping"],
)
@app.route("/api/users")
def users():
return {"users": []}
if __name__ == "__main__":
app.run()
The SDK patches Flask.full_dispatch_request — works with Flask blueprints, extensions, and all route patterns automatically.
FastAPI
Call init() before creating the FastAPI() application, or in a startup event.
from fastapi import FastAPI
import apimonitor
# Option A: Before app creation (recommended)
apimonitor.init(
api_key="your-api-key",
framework="fastapi",
environment="production",
)
app = FastAPI()
@app.get("/api/items")
def get_items():
return []
# Option B: Startup event
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
apimonitor.init(api_key="your-api-key", framework="fastapi")
yield
apimonitor.stop()
app = FastAPI(lifespan=lifespan)
The SDK patches starlette.applications.Starlette.__call__ and correctly reads the ASGI request body, then replays it to the actual handler — your endpoint sees the full body normally.
Generic (requests / httpx)
No web framework? The SDK still intercepts all outgoing HTTP calls made by your code.
import apimonitor
import requests
apimonitor.init(api_key="your-api-key")
# This call is automatically monitored
resp = requests.get("https://api.example.com/data")
httpx and direct urllib3 usage are intercepted in the same way.
Configuration Reference
All parameters can be passed to init() or set via environment variables (env vars take lower precedence than explicit kwargs).
init() Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
api_key |
str |
required | Your API Monitor platform key |
base_url |
str |
https://api.yourmonitor.com |
Platform base URL |
enable_validation |
bool |
False |
Enable Flow 1 — synchronous request gate |
enable_ingest |
bool |
True |
Enable Flow 2 — async metric capture |
framework |
str |
None |
e.g. "django", "flask", "fastapi" — shown in platform agents list |
environment |
str |
None |
e.g. "production", "staging" — shown in platform |
sampling_rate |
float |
1.0 |
Fraction of requests to capture (0.0–1.0). 0.1 = 10% |
batch_size |
int |
100 |
Flush metrics after this many are queued |
batch_timeout |
int |
10 |
Flush metrics after this many seconds even if batch is not full |
queue_maxsize |
int |
10000 |
Max in-memory queue depth (metrics dropped if exceeded) |
exclude |
list[str] |
[] |
Paths to skip. Supports exact (/health) and wildcard prefix (/internal/*) |
capture_request_body |
bool |
True |
Capture request bodies |
capture_response_body |
bool |
True |
Capture response bodies |
max_body_bytes |
int |
10240 |
Max bytes captured per body (10 KB default) |
sanitize_fields |
list[str] |
[] |
Extra field names to redact from bodies and headers |
extra_identity_headers |
list[str] |
[] |
Extra headers to use for caller identity (e.g. ["x-workspace-id"]) |
extra_identity_fields |
list[str] |
[] |
Extra JSON body fields for caller identity |
offline_mode |
bool |
True |
Buffer metrics in local SQLite when platform is unreachable |
local_db_path |
str |
~/.apimonitor_queue.db |
Path to the offline SQLite database |
max_offline_rows |
int |
50000 |
Max rows in the offline buffer (oldest are dropped when full) |
fail_behavior |
str |
"open" |
Validation failure behavior: "open" (allow) or "closed" (block) |
validate_timeout_ms |
int |
2000 |
Max time (ms) for a /validate call before falling back to fail_behavior |
check_cache_ttl_ms |
int |
500 |
How long to cache a validation allow/deny decision (ms) |
cb_failure_threshold |
int |
5 |
Consecutive failures before circuit breaker opens |
cb_window_seconds |
int |
10 |
Rolling window for measuring failures |
cb_recovery_seconds |
int |
30 |
Time before circuit breaker moves to HALF-OPEN and retries |
ingest_mode |
str |
"background" |
"background" (default) or "inline" (synchronous, for testing) |
heartbeat_interval |
int |
30 |
Seconds between liveness pings to the platform |
retry_initial_delay |
float |
1.0 |
Initial retry delay after a failed batch send (seconds) |
retry_max_delay |
float |
60.0 |
Maximum retry delay (exponential backoff cap) |
retry_multiplier |
float |
2.0 |
Backoff multiplier |
debug |
bool |
False |
Enable verbose SDK logging |
log_level |
str |
"ERROR" |
Python log level for SDK internals |
Environment Variables
Every parameter above has a corresponding environment variable. Use these for zero-change deployments in containerised environments.
| Environment Variable | Corresponding Parameter |
|---|---|
APIMONITOR_API_KEY |
api_key |
APIMONITOR_BASE_URL |
base_url |
APIMONITOR_ENABLE_VALIDATION |
enable_validation |
APIMONITOR_ENABLE_INGEST |
enable_ingest |
APIMONITOR_FRAMEWORK |
framework |
APIMONITOR_ENVIRONMENT |
environment |
APIMONITOR_SAMPLING_RATE |
sampling_rate |
APIMONITOR_BATCH_SIZE |
batch_size |
APIMONITOR_BATCH_TIMEOUT |
batch_timeout |
APIMONITOR_QUEUE_MAXSIZE |
queue_maxsize |
APIMONITOR_EXCLUDE |
exclude (comma-separated) |
APIMONITOR_CAPTURE_REQ_BODY |
capture_request_body |
APIMONITOR_CAPTURE_RESP_BODY |
capture_response_body |
APIMONITOR_MAX_BODY_BYTES |
max_body_bytes |
APIMONITOR_SANITIZE_FIELDS |
sanitize_fields (comma-separated) |
APIMONITOR_IDENTITY_HEADERS |
extra_identity_headers (comma-separated) |
APIMONITOR_IDENTITY_FIELDS |
extra_identity_fields (comma-separated) |
APIMONITOR_OFFLINE_MODE |
offline_mode |
APIMONITOR_LOCAL_DB_PATH |
local_db_path |
APIMONITOR_MAX_OFFLINE_ROWS |
max_offline_rows |
APIMONITOR_FAIL_BEHAVIOR |
fail_behavior |
APIMONITOR_VALIDATE_TIMEOUT_MS |
validate_timeout_ms |
APIMONITOR_CHECK_CACHE_TTL_MS |
check_cache_ttl_ms |
APIMONITOR_CB_FAILURE_THRESHOLD |
cb_failure_threshold |
APIMONITOR_CB_WINDOW_SECONDS |
cb_window_seconds |
APIMONITOR_CB_RECOVERY_SECONDS |
cb_recovery_seconds |
APIMONITOR_INGEST_MODE |
ingest_mode |
APIMONITOR_HEARTBEAT_INTERVAL |
heartbeat_interval |
APIMONITOR_DEBUG |
debug |
APIMONITOR_LOG_LEVEL |
log_level |
Docker / docker-compose example:
environment:
- APIMONITOR_API_KEY=your-api-key
- APIMONITOR_ENVIRONMENT=production
- APIMONITOR_SAMPLING_RATE=0.25
- APIMONITOR_EXCLUDE=/health,/metrics,/readyz
- APIMONITOR_SANITIZE_FIELDS=password,ssn,credit_card
Features In Depth
Request Validation Gate (Flow 1)
When enable_validation=True, the SDK makes a synchronous call to the platform before forwarding the request to your application. The platform evaluates your rate-limit rules and returns an allow/deny decision.
Performance protections built in:
- In-process LRU cache — repeated requests to the same endpoint by the same caller are cached for
check_cache_ttl_msms (default 500 ms). Cache hit = sub-millisecond, no network. - Circuit breaker — if the platform becomes unreachable, the breaker opens after
cb_failure_thresholdconsecutive failures and appliesfail_behaviorwithout making network calls. Recovers automatically aftercb_recovery_seconds. - Timeout — every validate call is bounded by
validate_timeout_ms(default 2 s). If the platform doesn't respond in time,fail_behavioris applied.
fail_behavior options:
| Value | Behavior |
|---|---|
"open" (default) |
Allow all requests when the platform is unreachable. Safe for production — your API keeps working. |
"closed" |
Block all requests when unreachable. Use for high-security scenarios. |
Blocked response format:
{
"error": "Rate limit exceeded",
"blocked_by": "apimonitor",
"retry_after": 30
}
Async Metric Ingest (Flow 2)
When enable_validation=False (or for custom events), metrics are captured asynchronously:
- Metric placed on an in-memory queue (non-blocking, < 1 µs).
- Background thread (
apimonitor-sender) drains the queue and accumulates a batch. - When batch reaches
batch_sizeorbatch_timeoutseconds elapses, the batch is POSTed to/ingest. - On failure, the batch is sent to exponential-backoff retry, and metrics are saved to offline storage.
Tuning for high-traffic apps:
apimonitor.init(
api_key="...",
batch_size=500, # larger batches, fewer HTTP calls
batch_timeout=5, # flush more frequently
sampling_rate=0.1, # capture only 10% of requests
queue_maxsize=50_000, # allow larger in-memory queue
)
Offline Buffering
When the platform is unreachable, metrics are persisted in a local SQLite database (WAL mode, safe for multi-process servers like Gunicorn).
- Default path:
~/.apimonitor_queue.db - Custom path:
local_db_path="/var/data/apimonitor.db" - Max rows:
max_offline_rows=50_000— when full, the oldest 10% of rows are dropped to make room - Auto-drain: When the platform becomes reachable again, the offline buffer is automatically drained in the background
Disable offline mode (e.g. ephemeral containers with no persistent disk):
apimonitor.init(api_key="...", offline_mode=False)
PII Sanitisation
The SDK automatically redacts the following patterns from all request/response bodies and headers before any data leaves your server:
| Pattern | Example | Redacted to |
|---|---|---|
| Email addresses | user@example.com |
[REDACTED] |
| Credit card numbers | 4111 1111 1111 1111 |
[REDACTED] |
| Phone numbers | +1 (555) 123-4567 |
[REDACTED] |
| Authorization header | Bearer eyJ... |
[REDACTED] |
x-api-key header |
any value | [REDACTED] |
cookie header |
any value | [REDACTED] |
Add your own fields to redact:
apimonitor.init(
api_key="...",
sanitize_fields=["password", "ssn", "secret_token", "credit_card_number"],
)
Fields are matched case-insensitively in both headers and JSON body (including nested objects).
Identity Resolution
The SDK resolves a stable, anonymised identity for every request caller using this priority chain:
- JWT — extracts
sub,user_id,userId,email, oruidclaim fromAuthorization: Bearerheader ortoken/access_tokencookie - Identity headers — checks
x-user-id,x-account-id,x-client-id,x-tenant-id,x-customer-id,x-actor-id - Request body — scans top-level and one-level-deep JSON for
user_id,userId,account_id,email,sub,uid, etc. - IP + User-Agent hash — fallback when no other signal is available
The resolved identity is always a SHA-256 hash (truncated to 16 hex chars) — raw PII is never stored.
Extend identity detection:
apimonitor.init(
api_key="...",
# Additional headers to check before the IP fallback
extra_identity_headers=["x-workspace-id", "x-org-id"],
# Additional body fields to check
extra_identity_fields=["workspace_id", "org_id"],
)
Sampling
Reduce data volume by capturing a fraction of requests:
apimonitor.init(
api_key="...",
sampling_rate=0.25, # capture 25% of traffic
)
1.0= 100% (default) — capture everything0.0= 0% — capture nothing (effectively disabled)- Sampling applies only to metric capture — validation decisions are never sampled
- Sampling rate can be updated live from the platform dashboard without restarting your server
Path Exclusions
Skip monitoring for health checks, metrics endpoints, static files, etc.:
apimonitor.init(
api_key="...",
exclude=[
"/health", # exact match
"/healthz",
"/readyz",
"/metrics",
"/static/*", # wildcard prefix — all paths starting with /static/
"/internal/*",
],
)
Exclusion rules support:
- Exact match:
/healthmatches only/health - Wildcard prefix:
/static/*matches/static/js/app.js,/static/css/main.css, etc.
Custom Events
Track business events that aren't HTTP requests:
monitor = apimonitor.init(api_key="...")
# Track a custom event with arbitrary metadata
monitor.track("user_signup", plan="pro", source="organic", country="US")
monitor.track("payment_processed", amount=99.99, currency="USD", user_id="u_123")
monitor.track("export_completed", format="csv", rows=15_420)
Custom events are batched and sent through the same async pipeline as HTTP metrics.
Diagnostics
Inspect the SDK's internal state at runtime:
monitor = apimonitor.get_monitor()
import pprint
pprint.pprint(monitor.diagnose())
Output example:
{
"started": True,
"pid": 12345,
"uptime_seconds": 3600.0,
"api_key_set": True,
"enable_validation": True,
"enable_ingest": True,
"fail_behavior": "open",
"sampling_rate": 1.0,
"batch_size": 100,
"batch_timeout": 10,
"ingest_mode": "background",
"circuit_breaker_state": "closed",
"offline_queue_size": 0,
"platform_base_url": "https://api.yourmonitor.com",
"patched_frameworks": ["django"],
"patched_libraries": ["requests", "httpx"],
"heartbeat_interval": 30,
}
Graceful Shutdown
The SDK registers an atexit handler automatically — when your process exits normally (SIGTERM, Ctrl+C, sys.exit()), any queued metrics are flushed before shutdown.
For explicit control (e.g. in tests or when using lifespan context managers):
# Stop the SDK and flush all remaining metrics
apimonitor.stop()
# Check if SDK is running
monitor = apimonitor.get_monitor()
if monitor:
print(monitor.diagnose())
Security Notes
- API key — keep your API key in environment variables, never hard-code it in source files.
- PII — the SDK scrubs known PII patterns before transmission, but you should also configure
sanitize_fieldsfor any application-specific sensitive fields. - Body capture — if your API handles highly sensitive data and body capture is not needed for your use case, disable it:
apimonitor.init( api_key="...", capture_request_body=False, capture_response_body=False, )
- TLS — all communication with the platform uses HTTPS. Ensure
base_urlis alwayshttps://. - Offline database — the SQLite queue file may contain request/response data. Protect it with appropriate filesystem permissions.
Troubleshooting
SDK appears to not send any data
- Verify
api_keyis correct. - Enable debug logging:
apimonitor.init(api_key="...", debug=True, log_level="DEBUG")
- Check
diagnose()— look atcircuit_breaker_stateandoffline_queue_size. - Confirm
base_urlis reachable from your server.
Validation always allows requests even when rules are set
- Ensure
enable_validation=Trueis passed toinit(). - Check
circuit_breaker_stateindiagnose()— if"open", the platform was unreachable andfail_behavior="open"is being applied.
High memory usage
Reduce the in-memory queue size and use a lower sampling rate:
apimonitor.init(
api_key="...",
queue_maxsize=1_000,
sampling_rate=0.1,
)
Request body is None in the platform (FastAPI)
Body capture requires that the request body has not already been consumed before the SDK reads it. The SDK handles this automatically by buffering and replaying the ASGI body stream. If you are using a custom ASGI middleware that reads the body first, ensure it also replays it.
Gunicorn / uWSGI workers not sending data
The SDK is fork-safe. However, ensure init() is called after the worker fork (e.g. in a post_fork hook or in AppConfig.ready()), not only in the main process.
Gunicorn post_fork hook:
# gunicorn.conf.py
def post_fork(server, worker):
import apimonitor
apimonitor.init(api_key="your-api-key", framework="django")
Changelog
v0.1.4
- Combined Flow 1 + Flow 2 into a single
/validateround-trip — no separate/ingestcall when validation is enabled - Added
Heartbeatclass replacing separateconfig_syncthread — liveness ping and config change detection in one thread - Live circuit breaker threshold updates from the platform control panel
- ASGI body read fixed for FastAPI — request body no longer null
- Added
frameworkandenvironmentmetadata to heartbeat payloads max_offline_rowscap added to prevent unbounded SQLite growthdiagnose()now reportspatched_frameworksandpatched_libraries
License
MIT License — see LICENSE for details.
© 2024 riken-khadela
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file api_monitor_sdk-0.1.4-py3-none-any.whl.
File metadata
- Download URL: api_monitor_sdk-0.1.4-py3-none-any.whl
- Upload date:
- Size: 40.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1522ddbc8d7c9b7779c422cd3adaad9591b7e9e55e445f0e200e4faae72e2a2c
|
|
| MD5 |
0cc79a9bada15e0e8b06ce3356112161
|
|
| BLAKE2b-256 |
9d09c99cffaa585200db4d8661a68b1f0d0ee80a9040ea511d4f9eebb37717b0
|