This guide walks you through the typical payment flow lifecycle with type-safe responses at every step using the nekuda Python SDK.

Overview

The nekuda payment flow for backend processing consists of these main steps:

1

Initialize Client

Set up the NekudaClient with your API key.

2

Create User Context

Obtain a UserContext by providing a unique user_id for your end-user.

3

Create Mandate

Describe the purchase intent by creating a MandateData object. This action returns a mandate_id.

4

Request Reveal Token

Use the mandate_id to get a short-lived, single-use token to access card details.

5

Reveal Card Details

Exchange the token for actual card information to complete the purchase (e.g., with a payment processor).

Important Workflow: A user_id is required to create a user context. A MandateData object must be created and submitted to the API via user.create_mandate() to obtain a mandate_id. This mandate_id is then required to request a reveal_token.

Complete Example

Here’s a full working example demonstrating the complete payment flow with the nekuda SDK:

payment_flow.py
from nekuda import MandateData, NekudaClient, NekudaError
import os

# Initialize client - uses NEKUDA_API_KEY from environment by default.
# Default base_url is https://api.nekuda.ai
client = NekudaClient.from_env()

# Or with explicit config (not needed if NEKUDA_API_KEY is set):
# client = NekudaClient(
#     api_key="sk_live_your_api_key",
#     # base_url="https://api.nekuda.ai", # Default, no need to specify for production
#     timeout=30,
#     max_retries=3
# )

# 1️⃣ Provide a unique user_id for your end-user.
user_id = "user_integration_test_123"
user = client.user(user_id)

try:
    # 2️⃣ Create a mandate (describes the purchase intent)
    mandate = MandateData(
        product="Premium Gold Subscription",
        price=99.99,
        currency="USD",
        merchant="nekuda Demo Store",
        merchant_link="https://app.nekuda.ai/demostore",
        product_description="Monthly access to all gold features"
    )

    # The user_id (user_integration_test_123) is implicitly passed.
    mandate_response = user.create_mandate(mandate)

    # Type-safe access to all fields!
    print(f"✅ Mandate Created: {mandate_response.mandate_id} for user: {user_id}")
    print(f"   Request ID: {mandate_response.request_id}")
    print(f"   Created At: {mandate_response.created_at}")

    # 3️⃣ Request reveal token using the mandate_id from the previous step.
    reveal_response = user.request_card_reveal_token(
        mandate_id=mandate_response.mandate_id  # Can use int or string
    )

    print(f"🔑 Reveal Token Generated: {reveal_response.reveal_token[:20]}...")
    print(f"   API Path for Reveal: {reveal_response.reveal_path}")

    # Check expiration if available
    if reveal_response.expires_at:
        print(f"   Token expires at: {reveal_response.expires_at}")

    # 4️⃣ Reveal card details using the single-use reveal_token.
    # The user_id is also implicitly passed here.
    card = user.reveal_card_details(reveal_response.reveal_token)

    # All fields are typed and validated!
    print(f"💳 Card Revealed: **** **** **** {card.card_number[-4:]}")
    print(f"   Expiry: {card.card_expiry_date}")  # Guaranteed MM/YY format
    print(f"   Name: {card.cardholder_name}")

    # Optional fields are properly typed
    if card.email:
        print(f"   Email: {card.email}")
    if card.billing_address:
        print(f"   Address: {card.billing_address}")

    print("🎉 Successfully completed the card reveal flow!")

except NekudaError as e:
    # Structured error handling
    print(f"❌ An error occurred: {e}")
    if hasattr(e, 'status_code'):
        print(f"   HTTP Status: {e.status_code}")
    if hasattr(e, 'code'):
        print(f"   Error Code: {e.code}")
    # For more details on error handling, see the Errors guide.

Response Models

All API methods return strongly-typed Pydantic models using the nekuda SDK:

class MandateCreateResponse:
    mandate_id: int           # Unique mandate identifier
    request_id: str          # Idempotency key
    customer_id: str         # This is the user_id you provided
    created_at: datetime     # Creation timestamp
    updated_at: datetime?    # Last update (optional)

Benefits of Typed Responses

1. IDE Autocomplete 🚀

Your IDE knows exactly what fields are available:

from nekuda import NekudaClient
client = NekudaClient.from_env()
user = client.user("some_user_id")
# Assume mandate_id is obtained
reveal_response = user.request_card_reveal_token(mandate_id=123)
reveal_response.  # IDE shows: reveal_token, reveal_path, expires_at

2. Type Safety 🛡️

# reveal_response = user.request_card_reveal_token(mandate_id=123)
# token = reveal_response.token  # AttributeError: no 'token' field, it's 'reveal_token'

3. Built-in Validation ✅

# Card details are automatically validated by the nekuda SDK
# card = user.reveal_card_details(reveal_token="...")
# card.card_expiry_date is guaranteed to be in MM/YY format
# card.card_number is validated to be 13-19 digits

4. Better Error Messages 📝

If the API returns invalid data, you get clear, actionable error messages from the nekuda SDK:

“Response validation failed for CardDetailsResponse: card_expiry_date: Card expiry must be in MM/YY format”

Advanced Features

URL Normalization

The SDK automatically normalizes URLs. The default base_url is https://api.nekuda.ai.

from nekuda import NekudaClient
# Uses https://api.nekuda.ai by default
client = NekudaClient(api_key="sk_live_...")

Response Validation

Flexible Mandate IDs

# Both work - SDK converts to string internally if needed
# reveal_response = user.request_card_reveal_token(mandate_id=123)
# reveal_response = user.request_card_reveal_token(mandate_id="123")

Production Considerations

Error Handling

Always wrap API calls in try-catch blocks. Refer to the Error Handling guide for details.

from nekuda import NekudaError
# try:
#     card = user.reveal_card_details(token)
#     # Process payment with card details
# except NekudaError as e:
#     # Log error and handle gracefully
#     logger.error(f"Payment failed: {e}")
#     # Return error response to user

Token Security

Reveal tokens (reveal_token) are single-use and time-limited. Store them securely if necessary, but ideally, use them immediately after generation.

Rate Limiting

The SDK automatically handles rate limiting with exponential backoff based on max_retries. For very high-volume applications, monitor API usage and consider if additional client-side rate limiting logic is beneficial.

What’s Next?