Transparent LLM usage tracking — OpenAI and Anthropic wrappers that send cost and token metrics to Nightswatch.
Project description
Nightswatch SDK — Installation & Integration Guide
Base URL: https://api.nightswatch.work
Requirements
- Python 3.9+
- An active Nightswatch account and API key (
nw_prefix)
Installation
pip install nightswatch-sdk
To enable NER-based PII redaction (recommended for production):
pip install nightswatch-sdk spacy
python -m spacy download en_core_web_sm
Without spacy, PII redaction falls back to regex-only (SSN, credit cards, emails, phones, IP addresses still redacted).
Quick Start
1. Initialize
Call nightswatch.init() once at application startup, before any LLM calls.
import nightswatch
nightswatch.init(
api_key="nw_your_api_key_here",
default_team="backend",
default_service="chat-api",
)
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
api_key |
string | Yes | — | Your API key from the Nightswatch dashboard |
default_team |
string | No | "unknown" |
Team label applied to all events unless overridden per-call |
default_service |
string | No | "unknown" |
Service/product label applied to all events |
2. Wrap your LLM client
Pass your existing client to nightswatch.wrap(). The returned object is a drop-in replacement — all existing call signatures remain unchanged.
client = nightswatch.wrap(client)
Provider Integration
OpenAI
import nightswatch
from openai import OpenAI
nightswatch.init(api_key="nw_your_api_key_here", default_team="backend", default_service="chat-api")
client = nightswatch.wrap(OpenAI())
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Explain async/await in Python"}],
)
print(response.choices[0].message.content)
Anthropic
import nightswatch
import anthropic
nightswatch.init(api_key="nw_your_api_key_here", default_team="ml-team", default_service="summarizer")
client = nightswatch.wrap(anthropic.Anthropic())
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Summarize this document..."}],
)
print(message.content[0].text)
Gemini support is on the roadmap (v1.1). The SDK currently wraps
openai.OpenAIandanthropic.Anthropicclients. Gemini integration ships in the next release. For now, instrument Gemini via the REST API below.
REST API (Manual Instrumentation)
If you are not using Python, or need to instrument a language without a native SDK, send events directly to the ingestion endpoint.
POST /v1/ai-usage/events
Authorization: Bearer nw_your_api_key_here
Content-Type: application/json
Request body
| Field | Type | Required | Description |
|---|---|---|---|
provider |
string | Yes | LLM provider: openai, anthropic, google, etc. |
model |
string | Yes | Model name, e.g. gpt-4o, claude-sonnet-4-6, gemini-2.5-flash |
prompt_tokens |
integer | Yes | Input token count |
completion_tokens |
integer | Yes | Output token count |
total_tokens |
integer | Yes | Sum of prompt + completion tokens |
cost_usd |
float | No | Cost in USD (computed server-side from token counts if 0) |
team |
string | No | Team label for cost attribution |
service |
string | No | Service/product label |
user_id |
string | No | End-user identifier (for per-user analytics) |
metadata |
object | No | Arbitrary key-value pairs; PII is redacted before storage |
Example
curl -X POST https://api.nightswatch.work/v1/ai-usage/events \
-H "Authorization: Bearer nw_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"provider": "openai",
"model": "gpt-4o",
"prompt_tokens": 142,
"completion_tokens": 89,
"total_tokens": 231,
"cost_usd": 0.0,
"team": "backend",
"service": "chat-api"
}'
Response 200
{
"status": "ok",
"event_id": "64d3f2a1-..."
}
Errors
| Code | Reason |
|---|---|
| 401 | Missing or invalid API key |
| 422 | Required field missing or wrong type |
Batch Ingestion
Send up to 100 events in a single request.
POST /v1/ai-usage/events/batch
Authorization: Bearer nw_your_api_key_here
Content-Type: application/json
Request body
{
"events": [
{ "provider": "openai", "model": "gpt-4o", "prompt_tokens": 100, "completion_tokens": 50, "total_tokens": 150 },
{ "provider": "anthropic", "model": "claude-3-haiku-20240307", "prompt_tokens": 200, "completion_tokens": 80, "total_tokens": 280 }
]
}
Configuration Options
Per-call overrides
Override default_team or default_service for a specific call by passing keyword arguments (where supported by the provider wrapper):
response = client.chat.completions.create(
model="gpt-4o",
messages=[...],
nightswatch_team="data-science",
nightswatch_service="embeddings",
)
Data Retention
Retention is configured per-tenant via the API. Valid values: 7, 30, or 90 days.
# Get current retention setting
curl https://api.nightswatch.work/v1/tenants/retention \
-H "Authorization: Bearer nw_your_api_key_here"
# Update retention to 30 days
curl -X PUT https://api.nightswatch.work/v1/tenants/retention \
-H "Authorization: Bearer nw_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"retention_days": 30}'
Response
{ "tenant_id": "...", "retention_days": 30 }
Events older than the configured window are automatically deleted nightly.
PII Redaction
All prompt text and metadata values are automatically redacted before storage and AI analysis. The following are detected and replaced with [REDACTED]:
| PII Type | Examples |
|---|---|
| SSN | 123-45-6789 |
| Credit card numbers | Visa, Mastercard, Amex (with or without spaces/dashes) |
| Email addresses | alice@example.com |
| Phone numbers | US, UK, Indian formats |
| IP addresses | 192.168.1.1 |
| Aadhaar / PAN | Indian government IDs |
| Named entities | Person names, organizations, locations (requires spacy) |
Raw prompt text is never stored. The Nightswatch AI risk classifier only receives token counts, model name, provider, cost, team, and service — never prompt content.
To verify redaction is active in your environment:
from backend.services.pii_redactor import redact
print(redact("Contact alice@example.com or call 555-867-5309"))
# → "Contact [REDACTED] or call [REDACTED]"
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
401 Unauthorized |
Invalid or missing API key | Confirm key starts with nw_ and is passed in Authorization: Bearer header |
422 Unprocessable Entity |
Required field missing | Ensure provider, model, prompt_tokens, completion_tokens, total_tokens are present |
| Events not appearing in dashboard | Clock skew on event timestamp |
Use UTC timestamps; Nightswatch rejects events more than 24 h in the past |
Cost shows 0.0 |
cost_usd not sent |
Either send cost_usd from your LLM client, or Nightswatch computes it from token counts automatically for known models |
| spaCy NER not running | Package not installed | pip install spacy && python -m spacy download en_core_web_sm; falls back to regex if unavailable |
ValueError: retention_days must be... |
Invalid retention value | Only 7, 30, or 90 are accepted |
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
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 nightswatch_sdk-0.1.2.tar.gz.
File metadata
- Download URL: nightswatch_sdk-0.1.2.tar.gz
- Upload date:
- Size: 108.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e98142eac1b9f52528506f4b2093d0b6aa8cb5ca913e5c808142acac357352bb
|
|
| MD5 |
7102e9a87dd44dc91515154004927d35
|
|
| BLAKE2b-256 |
3bd09c0db289703291587427435d14d407f013133d42245e80868561d703474f
|
Provenance
The following attestation bundles were made for nightswatch_sdk-0.1.2.tar.gz:
Publisher:
publish-sdk.yml on Nightswatch-Work/nightswatch-core
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nightswatch_sdk-0.1.2.tar.gz -
Subject digest:
e98142eac1b9f52528506f4b2093d0b6aa8cb5ca913e5c808142acac357352bb - Sigstore transparency entry: 1954607611
- Sigstore integration time:
-
Permalink:
Nightswatch-Work/nightswatch-core@c00c1820cffa31d63669f48f34c64e18f03677e5 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Nightswatch-Work
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-sdk.yml@c00c1820cffa31d63669f48f34c64e18f03677e5 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file nightswatch_sdk-0.1.2-py3-none-any.whl.
File metadata
- Download URL: nightswatch_sdk-0.1.2-py3-none-any.whl
- Upload date:
- Size: 11.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
76d63e96f5f6b6ccd7ec0fc9d462d154f7b1a2b5c6ef3478808129c50b420059
|
|
| MD5 |
ce99685555423c443561be2d5fa97a12
|
|
| BLAKE2b-256 |
615786f5eaf1ad3b8cef234374162cff4ed8661ec02d6ff3464d80203ac40a12
|
Provenance
The following attestation bundles were made for nightswatch_sdk-0.1.2-py3-none-any.whl:
Publisher:
publish-sdk.yml on Nightswatch-Work/nightswatch-core
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nightswatch_sdk-0.1.2-py3-none-any.whl -
Subject digest:
76d63e96f5f6b6ccd7ec0fc9d462d154f7b1a2b5c6ef3478808129c50b420059 - Sigstore transparency entry: 1954607692
- Sigstore integration time:
-
Permalink:
Nightswatch-Work/nightswatch-core@c00c1820cffa31d63669f48f34c64e18f03677e5 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Nightswatch-Work
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-sdk.yml@c00c1820cffa31d63669f48f34c64e18f03677e5 -
Trigger Event:
workflow_dispatch
-
Statement type: