Python SDK

Complete reference for the slootly Python package.

Installation

bash
pip install slootly

Initialization

Simple (API key only)

python
from slootly import Slootly

gate = Slootly("sk_live_your_api_key")

Full Configuration

python
from slootly import Slootly

gate = Slootly(
    "sk_live_your_api_key",
    base_url="https://api.slootly.dev/v1",       # default
    cache_ttl=300,                                 # 5 minutes in seconds, default
    offline_verification=True,                     # default
    timeout=5.0,                                   # 5 seconds, default
    checkout_base_url="https://pay.slootly.dev",  # default
)

Constructor Parameters

ParameterTypeDefaultDescription
api_keystr(required)API key starting with sk_live_ or sk_test_
base_urlstrhttps://api.slootly.dev/v1Slootly API base URL
cache_ttlint300In-memory cache TTL in seconds
offline_verificationboolTrueEnable JWT offline verification
timeoutfloat5.0HTTP request timeout in seconds
checkout_base_urlstrhttps://pay.slootly.devHosted checkout base URL

Async Context Manager

python
async with Slootly("sk_live_...") as gate:
    sub = await gate.check(user_id, "telegram")
    # Resources are cleaned up automatically on exit

gate.check()

Check subscription status for a user on a platform. This is the primary SDK method. Checks in order: in-memory cache, JWT offline verification, then API call.

signaturepython
async def check(self, user_id: str, platform: str) -> Subscription

Parameters

ParameterTypeDescription
user_idstrPlatform-specific user ID (e.g., Telegram from.id as string)
platformstr'telegram' | 'discord' | 'whatsapp' | 'slack' | 'custom'

Returns: Subscription

PropertyTypeDescription
activeboolWhether the user has an active subscription
planOptional[str]Plan slug (e.g., 'pro'). None if no subscription
plan_idOptional[str]Plan ID
statusstr'active' | 'trialing' | 'past_due' | 'canceled' | 'expired' | 'none'
current_period_startOptional[datetime]Current billing period start
current_period_endOptional[datetime]Current billing period end
cancel_at_period_endboolWhether the sub will cancel at period end
trial_endOptional[datetime]Trial end date. None if not on trial
subscriber_idOptional[str]Slootly subscriber ID
platformsList[str]All platforms linked to this subscriber
metadataDict[str, Any]Developer-defined metadata

Example

python
sub = await gate.check("123456789", "telegram")

if sub.active:
    print(f"Plan: {sub.plan}")           # "pro"
    print(f"Status: {sub.status}")       # "active"
    print(f"Expires: {sub.current_period_end}")
    print(f"Platforms: {sub.platforms}")  # ["telegram", "discord"]
else:
    print("No active subscription")

gate.checkout_url()

Generate a checkout URL for a user to subscribe to a plan. This is a synchronous method -- no API call needed.

signaturepython
def checkout_url(
    self,
    user_id: str,
    plan_slug: str,
    platform: str,
    *,
    success_url: Optional[str] = None,
    cancel_url: Optional[str] = None,
    email: Optional[str] = None,
    metadata: Optional[Dict[str, str]] = None,
) -> str

Parameters

ParameterTypeDescription
user_idstrPlatform-specific user ID
plan_slugstrPlan slug (e.g., 'pro')
platformstrPlatform name
success_urlOptional[str]Custom success redirect URL
cancel_urlOptional[str]Custom cancel redirect URL
emailOptional[str]Pre-fill customer email
metadataOptional[Dict[str, str]]Additional metadata

Example

python
# Simple
url = gate.checkout_url("123456789", "pro", "telegram")
# => https://pay.slootly.dev/checkout/pro?uid=123456789&platform=telegram

# With options
url = gate.checkout_url(
    "123456789", "pro", "telegram",
    success_url="https://t.me/mybot",
    email="user@example.com",
    metadata={"referral": "homepage"},
)

gate.get_plans()

Fetch all active plans for the project.

signaturepython
async def get_plans(self) -> List[Plan]

Plan Dataclass

PropertyTypeDescription
idstrPlan ID
namestrDisplay name
slugstrURL-safe identifier
descriptionOptional[str]Plan description
amountintPrice in smallest currency unit (cents)
currencystrCurrency code (e.g., 'usd')
intervalstr'day' | 'week' | 'month' | 'year'
interval_countintBilling interval count
trial_daysintFree trial days
featuresList[str]Feature list
metadataDict[str, Any]Custom metadata

Example

python
plans = await gate.get_plans()

for plan in plans:
    print(f"{plan.name}: ${plan.amount / 100}/{plan.interval}")
    print(f"Features: {', '.join(plan.features)}")

gate.cancel_subscription()

Cancel a user's subscription. The subscription remains active until the end of the current billing period.

python
sub = await gate.check("123456789", "telegram")
if sub.subscriber_id:
    await gate.cancel_subscription(sub.subscriber_id)
    # Subscription remains active until sub.current_period_end

gate.verify_webhook()

Verify a webhook signature and parse the event.

signaturepython
def verify_webhook(
    self,
    payload: str,      # raw request body
    signature: str,    # Slootly-Signature header
    secret: str,       # webhook secret (whsec_...)
) -> WebhookEvent
python
# FastAPI example
from fastapi import Request, Header

@app.post("/webhooks/slootly")
async def handle_webhook(
    request: Request,
    slootly_signature: str = Header(..., alias="Slootly-Signature"),
):
    payload = await request.body()
    event = gate.verify_webhook(
        payload.decode(),
        slootly_signature,
        "whsec_your_webhook_secret",
    )

    if event.type == "subscription.created":
        print(f"New subscriber: {event.data.subscriber_id}")
    elif event.type == "payment.failed":
        print(f"Payment failed: {event.data.subscriber_id}")

    return {"ok": True}

WebhookEvent Type

PropertyTypeDescription
idstrUnique event ID
typestrEvent type (e.g., 'subscription.created')
created_atdatetimeEvent timestamp
project_idstrProject ID
dataWebhookEventDataEvent payload

WebhookEventData

PropertyTypeDescription
subscriber_idOptional[str]Subscriber ID
subscription_idOptional[str]Subscription ID
planOptional[Dict[str, str]]Plan info (id, name, slug)
statusOptional[str]Subscription status
platformOptional[str]Platform name
platform_user_idOptional[str]Platform user ID
current_period_startOptional[str]Period start (ISO 8601)
current_period_endOptional[str]Period end (ISO 8601)
cancel_at_period_endOptional[bool]Cancel at period end flag
metadataOptional[Dict[str, Any]]Custom metadata

Cleanup

python
# Clear all cached subscription data
gate.clear_cache()

# Clean up SDK resources (close HTTP sessions)
await gate.close()

# Or use async context manager for automatic cleanup
async with Slootly("sk_live_...") as gate:
    sub = await gate.check(user_id, "telegram")

Error Handling

python
from slootly import Slootly
from slootly.errors import ValidationError, AuthenticationError, NetworkError

try:
    sub = await gate.check(user_id, "telegram")
except ValidationError as e:
    # Invalid input (empty user_id, invalid platform)
    print(f"Validation error: {e}")
except AuthenticationError as e:
    # Invalid API key
    print(f"Auth error: {e}")
except NetworkError as e:
    # Network issue, API unreachable
    print(f"Network error: {e}")

Info

The SDK gracefully handles network failures. If the API is unreachable and a cached JWT is available, the SDK falls back to JWT verification. Your bot continues working even if Slootly is temporarily down.