Before jumping into advanced usage, it’s worth getting familiar with the building blocks that form the nekuda SDK public surface.

NekudaClient

The single entry-point to all network operations that:

  • Manages an internal persistent httpx.Client with automatic retry & back-off for 5xx / 429 responses
  • Can be instantiated directly or via the convenience constructor NekudaClient.from_env()
  • Automatically normalizes URLs (adds https://, removes trailing slashes). The default base_url is https://api.nekuda.ai.
from nekuda import NekudaClient

# Production client

client = NekudaClient(api*key="sk_live*...")

# Client pointing to a different API endpoint (e.g. staging)

# client = NekudaClient(api*key="sk_test*...", base_url="https://staging-api.nekuda.ai")

UserContext

A lightweight wrapper returned by client.user(user_id) that automatically injects the user_id header on every call. A user_id is mandatory for all user-specific operations like creating mandates or revealing cards.

from nekuda import NekudaClient, MandateData

client = NekudaClient.from_env()

# You must provide a unique user_id for each of your users.
user_id = "user_unique_identifier_123"
user = client.user(user_id)

# Example MandateData (details below)
mandate_details = MandateData(
    product="Example Product",
    price=10.99,
    currency="USD",
    merchant="YourStore"
)

# The user_id is now automatically included in the following calls:
# 1. Create a mandate (intent to purchase)
mandate_response = user.create_mandate(mandate_details)

# 2. A mandate must be created first to obtain a mandate_id.
# This mandate_id is then used to request a card reveal token.
reveal_response = user.request_card_reveal_token(mandate_response.mandate_id)

# 3. Exchange the token for card details
card_details_response = user.reveal_card_details(reveal_response.reveal_token)

Behind the scenes no extra network round-trip happens for client.user(user_id)UserContext is just a convenience object that stores the user_id.

MandateData

A small dataclass that represents the user’s intent to purchase. It must be successfully submitted via user.create_mandate(mandate_data) to obtain a mandate_id before you can request a card reveal token.

from nekuda import MandateData

mandate = MandateData(
    product="Premium Subscription",
    price=49.99,
    currency="USD",
    merchant="nekuda Corp",
    # Optional fields:
    merchant_link="https://app.nekuda.ai/premium", # Corrected example link
    product_description="Access to all premium features",
    confidence_score=0.95
)

Client-side validation runs in __post_init__. Invalid payloads raise NekudaValidationError before any HTTP request is made.

Response Models 🎯

All API methods now return typed Pydantic models instead of raw dictionaries. This provides:

  • Type safety - Your IDE knows exactly what fields are available
  • Validation - Card numbers, expiry dates, etc. are automatically validated
  • Better errors - Clear messages when API returns unexpected data

Key Response Models

class CardRevealTokenResponse:
    reveal_token: str         # The one-time token
    reveal_path: str          # API endpoint for reveal
    expires_at: datetime?     # Optional expiration

Example with Type Safety

# user_id had to be passed in each call
token_dict = client.request_card_reveal_token(user_id="some_user", mandate_id="m_123")
token = token_dict["reveal_token"]  # 😟 Hope the key exists!

Error Hierarchy

All exceptions inherit from a common NekudaError base class so a single except block can catch everything:

NekudaError
 ├─ NekudaApiError
 │   ├─ AuthenticationError     # 401
 │   ├─ InvalidRequestError     # 4xx / 404
 │   ├─ CardNotFoundError       # 404 (specific case)
 │   ├─ RateLimitError          # 429 (retry-able)
 │   └─ ServerError             # 5xx (retry-able)
 ├─ NekudaConnectionError       # network issues
 └─ NekudaValidationError       # client-side validation failed

NEW: The SDK now detects and properly handles HTML error pages (nginx errors, gateway timeouts) that sometimes occur during infrastructure issues.

For a deep-dive see the dedicated Error Handling page.

Advanced Features 🚀

Response Validation

The SDK automatically validates all API responses:

URL Normalization

The default base_url is https://api.nekuda.ai. You generally don’t need to set this unless using a staging or mock server.

# Client uses https://api.nekuda.ai by default
client_prod = NekudaClient(api_key="sk_live_...")

# Example for staging (if needed)
# client_staging = NekudaClient(api_key="sk_test_...", base_url="https://staging-api.nekuda.ai")

Thread-safety & Re-usability

NekudaClient is designed to be cheap to instantiate but you can (and should) reuse a single instance across the lifetime of your process for maximum performance. Internally it lazily creates the underlying HTTP session the first time you make a request.

Future Roadmap

Async Support Coming Soon 🔮 The public API has been designed with parity in mind, so an AsyncNekudaClient will drop in without breaking changes.

What’s Next?