Skip to content
Indiana
Dashboard
Docs
Browse docs / Python

Python

Synchronous and asynchronous server clients for Customers, holdings, entitlement checks and usage.

API v1Updated

Install

terminalShell
python -m pip install "https://tinker.cash/downloads/python/tinkerlandai_indiana_sdk-0.6.1-py3-none-any.whl"

Version 0.6.1, Python 3.11+. Distributed from tinker.cash rather than PyPI; the distribution is tinkerlandai-indiana-sdk and the import is indiana.

Related: pip, uv and requirements.txt

Synchronous client

Set INDIANA_API_KEY, INDIANA_PROJECT_ID and INDIANA_API_URL (https://api.tinker.cash in production). The event and Feature must already be configured in that App/environment.

usage.pyPython
import os from indiana import Indiana with Indiana(    api_key=os.environ["INDIANA_API_KEY"],    project_id=os.environ["INDIANA_PROJECT_ID"],    environment="sandbox",    base_url=os.environ["INDIANA_API_URL"],) as client:    result = client.get_or_create_customer(external_user_id="merchant-user-123")    customer_id = result["customer"]["id"]     holdings = client.read_holdings(customer_id=customer_id)    check = client.check_entitlement(        customer_id=customer_id,        internal_feature_id=os.environ["INDIANA_FEATURE_ID"],        quantity="1",    )    if check["check"]["allowed"]:        usage = client.track_usage(            customer_id=customer_id,            event_name="image_generated",            quantity="1.25",            properties={"quality": "hd"},            idempotency_key="merchant-event-123",        )        for receipt in usage["receipts"]:            print(receipt["accepted"], receipt.get("reason"))

An entitlement check only observes access; usage is a separate write. HTTP success does not mean a debit was accepted: inspect each receipt's accepted and reason.

Asynchronous client

holdings.pyPython
import os from indiana import AsyncIndiana  async def read_customer_holdings(external_user_id: str):    async with AsyncIndiana(        api_key=os.environ["INDIANA_API_KEY"],        project_id=os.environ["INDIANA_PROJECT_ID"],        environment="sandbox",        base_url=os.environ["INDIANA_API_URL"],    ) as client:        result = await client.get_or_create_customer(external_user_id=external_user_id)        return await client.read_holdings(customer_id=result["customer"]["id"])

Reuse one client in a long-running service and close it on shutdown with close() (sync) or await aclose() (async). Both accept timeout in seconds (default 10, maximum 120) and an HTTPX transport= for tests or custom networking. Ambient proxies and cookies are not used and redirects are not followed.

Methods

All arguments are keyword-only; returned TypedDicts keep wire keys such as `customerId`.
MethodInputs and behaviorScope
get_or_create_customerexternal_user_id or email; optional name, attributes, fingerprint. Existing records are not patched.customers:write
get_customerExactly one of customer_id or external_user_id.customers:read
list_customersFilters with explicit offset/limit; returns customers, hasMore, total.customers:read
update_customerA retained request_id, one lookup and a partial patch: name, email, attributes, remove_attributes, add_tags, remove_tags.customers:write
read_holdingscustomer_id, optional entity_id.entitlements:read
check_entitlementcustomer_id, internal_feature_id; positive decimal-string quantity, optional properties/entity_id.entitlements:read
track_usagecustomer_id, a retained idempotency_key, exactly one of event_name or internal_feature_id; decimal-string quantity, optional properties/entity_id.credits:consume

All arguments are keyword-only; returned TypedDicts keep wire keys such as `customerId`.

  • get_or_create_customer

    Inputs and behavior
    external_user_id or email; optional name, attributes, fingerprint. Existing records are not patched.
    Scope
    customers:write
  • get_customer

    Inputs and behavior
    Exactly one of customer_id or external_user_id.
    Scope
    customers:read
  • list_customers

    Inputs and behavior
    Filters with explicit offset/limit; returns customers, hasMore, total.
    Scope
    customers:read
  • update_customer

    Inputs and behavior
    A retained request_id, one lookup and a partial patch: name, email, attributes, remove_attributes, add_tags, remove_tags.
    Scope
    customers:write
  • read_holdings

    Inputs and behavior
    customer_id, optional entity_id.
    Scope
    entitlements:read
  • check_entitlement

    Inputs and behavior
    customer_id, internal_feature_id; positive decimal-string quantity, optional properties/entity_id.
    Scope
    entitlements:read
  • track_usage

    Inputs and behavior
    customer_id, a retained idempotency_key, exactly one of event_name or internal_feature_id; decimal-string quantity, optional properties/entity_id.
    Scope
    credits:consume

Omitted fields stay omitted; None is not an alias for omission. Quantities are decimal strings, never floats. Import the generated TypedDicts from indiana.models.

Errors and recovery

errors.pyPython
from indiana import IndianaError try:    holdings = client.read_holdings(customer_id=customer_id)except IndianaError as error:    print(error.code, error.status, error.request_id, error.retryable, error.recovery)

IndianaError exposes code, status, request_id, retryable, recovery, and optional reason and retry_after_seconds; it never retains the Key or payload. There are no automatic retries: persist the idempotency_key or request_id before sending and replay the same value and input after an uncertain result. TIMEOUT, NETWORK_ERROR and cancellation do not mean rollback.

Other operations

The Python SDK covers seven operations: Customers, holdings, entitlement checks and usage. Call other server operations, such as sessions, payments, offers, invoices, balance management and Funnel decisions, directly over HTTP with the same Server Key, X-Indiana-Protocol: 0.6 and a JSON body, and handle failures by the error object's code and recovery.

sessions.pyPython
import os import httpx # An operation outside the SDK: issue a Customer Token (sessions:create).response = httpx.post(    f"{os.environ['INDIANA_API_URL']}/api/v1/customer-sessions",    headers={        "Authorization": f"Bearer {os.environ['INDIANA_API_KEY']}",        "X-Indiana-Protocol": "0.6",    },    json={"customerId": "<CUSTOMER_UUID>"},    timeout=10,)body = response.json()if response.is_error:    raise RuntimeError(body["error"]["code"])customer_token = body["customerToken"]

Related: All operations·Request conventions