Official Python SDK for the Sonny Labs AI firewall — install, first scan, error handling, idempotency, retries, self-hosted, and webhook verification.
The Python SDK targets Python 3.10+. It uses the standard library and
httpx under the hood, exposes typed dataclasses for every request and
response, and ships ready-made helpers for idempotency, retries, and
webhook signature verification.
Looking for the symbol-by-symbol reference? The full
auto-generated API reference (SonnyLabsClient, constructor
arguments, exception classes, verify_webhook) lives at
Python SDK reference. This page is the
conceptual quickstart — install, first scan, error handling,
idempotency, retries, self-hosted, and webhook verification.
Customer-facing quickstart for the official Sonny Labs Python SDK
(sonnylabs). Wraps the /v1/* surface defined in
REST API reference.
Coming soon to PyPI — until publication, install from the source
tree under sdks/python/ in this repository.
The SDK authenticates with a scoped API key (sk_live_… or
sk_test_…). For a runtime scanner the smallest viable scope is
scans:write. The full lifecycle, including scope choice, rotation,
and revocation, is documented in
the API key endpoints in the REST reference.
The short version:
Sign in to the dashboard, pick the org you want the key to belong
to, and open API keys.
Click Create key, give it a human-readable name, and select the
scans:write scope.
Copy the plaintext secret from the success modal — it is shown
exactly once. Store it in your secrets manager before closing the
dialog.
create_scan maps to POST /v1/scans (operationId: createScan).
The kind field discriminates between content scans (a single prompt
or response) and toolset scans (an MCP/native tool inventory). See
the
POST /v1/scans reference
for every field.
Every non-2xx response is parsed as an RFC 9457
application/problem+json envelope and raised as a SonnyLabsError
subclass. Pattern-match on the stable dot-namespaced code:
from sonnylabs import SonnyLabsClientfrom sonnylabs.errors import ( SonnyLabsError, AuthenticationError, RateLimitError, ValidationError,)client = SonnyLabsClient(api_key=os.environ["SONNY_API_KEY"])try: scan = client.create_scan( kind="content", surface="user_message", content={"type": "text", "text": user_prompt}, )except AuthenticationError as e: if e.code == "auth.api_key.expired": # Rotate the credential and retry once on the new value. ... elif e.code == "auth.api_key.revoked": # Operator revoked the key — surface a re-credential UI. ... else: raiseexcept RateLimitError as e: # Honour Retry-After; see "Retry behavior" below. ...except ValidationError as e: # e.errors is a list of {path, code, detail} per-field problems. for field in e.errors: print(f"{field.path}: {field.code}")except SonnyLabsError as e: # Catch-all for any other Problem-shaped failure. print(f"{e.status} {e.code}: {e.detail}")
Common code values you should expect to see:
Code
When
auth.api_key.invalid
The bearer secret didn't parse or didn't match any key.
auth.api_key.revoked
The key was deleted via DELETE /v1/api-keys/{id}.
auth.api_key.expired
The key is past its expiry (once expiry lands; see docs/api-keys.md).
auth.scope.missing
The key authenticated but lacks the scope this endpoint demands.
validation.field_invalid
One or more errors[] entries describe the offending field.
idempotency.key_reuse_mismatch
Same Idempotency-Key was reused with a different body.
Every SonnyLabsError carries status, code, title, detail,
request_id, trace_id, and a problem attribute holding the raw
envelope for forward-compatibility.
Every POST accepts an Idempotency-Key header. The server stores the
key plus a body hash for 24 hours; reusing the same key with the same
body replays the original response, and reusing it with a different
body returns 409 idempotency.key_reuse_mismatch.
The Python SDK auto-generates a UUIDv4 idempotency key for every
create_scan call so retries are safe by default. Pass an explicit key
when your caller already has a stable identifier (a workflow run ID,
an upstream message ID, etc.):
Disable auto-generation entirely (e.g. when the upstream proxy is
already handling dedup) by passing idempotency_key=None and
configuring the client with auto_idempotency=False.
The SDK transparently retries 429 Too Many Requests and
503 Service Unavailable responses up to 3 times with exponential
backoff. When the response carries a Retry-After header, the SDK
honors it instead of computing its own delay.
5xx responses other than 503 are not retried — they signal a
permanent server-side error for that request, and silently retrying
risks duplicating side effects.
If you would rather drive retries yourself, set max_retries=0 and
catch RateLimitError / ServerError from your own retry loop. The
retry_after_seconds attribute on RateLimitError exposes the parsed
Retry-After value for that purpose.
The SDK speaks the same /v1/* surface — the only thing that changes
is where the requests are sent. API keys are issued by the self-hosted
control plane just like in SaaS; the format and scope semantics are
identical.
Outbound webhooks (scan.allowed, scan.flagged, scan.warned,
scan.blocked) are signed with HMAC-SHA256 over the canonical
{t}.{body} payload. The SDK ships a verify_webhook helper that
validates the signature and the replay window in one call. See
Webhooks for the signing scheme, the full code
sample, and what to do on signature mismatch.