This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Privy Python SDK (v0.2.0) - An auto-generated OpenAPI client for the Privy API, a Web3 identity and wallet platform. The SDK is generated by Stainless with custom extensions in lib/ for enhanced cryptographic functionality.
PrivyAPI / AsyncPrivyAPI (Client Layer)
↓
Resources: users, wallets, policies, transactions, fiat (Resource Layer)
↓
SyncAPIClient / AsyncAPIClient (HTTP Client Layer - httpx)
↓
Pydantic Models & Type System
-
lib/- Custom extensions (preserved during code generation)hpke.py- HPKE encryption/decryption (P-256 + ChaCha20-Poly1305)authorization_signatures.py- ECDSA request signingusers.py- Extended Users resource withcreate_with_jwt_auth()wallets.py- Extended Wallets resource withgenerate_user_signer()http_client.py- Custom HTTP client configuration
-
resources/- Auto-generated API resourcesusers.py- User CRUD operationswallets/- Wallet operations (nested: balance, transactions)policies.py,key_quorums.py,transactions.pyfiat/- Fiat onramp/offramp operations
-
types/- Pydantic type definitions (~34 files)- Pattern:
*_params.pyfor request parameters,*_response.pyfor responses - Heavy use of
NotGivenfor truly optional parameters
- Pattern:
-
Core modules:
_client.py- Main PrivyAPI and AsyncPrivyAPI clients_base_client.py- HTTP client abstraction with retry/streaming_models.py- Pydantic BaseModel extensions_response.py- Response parsing (APIResponse, AsyncAPIResponse)_exceptions.py- Exception hierarchy (PrivyAPIError → APIStatusError → specific errors)
This SDK is auto-generated from OpenAPI spec by Stainless.
IMPORTANT:
- Core API resources regenerate on spec updates - do NOT manually modify files with "File generated from our OpenAPI spec by Stainless" header
- Custom code belongs in
lib/directory (preserved during regeneration) - Extensions use inheritance pattern:
class UsersResource(BaseUsersResource)
All resources have both synchronous and asynchronous variants:
# Sync
class UsersResource(SyncAPIResource):
def create(...) -> User: ...
# Async
class AsyncUsersResource(AsyncAPIResource):
async def create(...) -> User: ...When adding custom methods, always implement both variants.
Resources support response wrapper patterns:
# Standard usage
user = client.users.get("user_id")
# Get full HTTP response
response = client.with_raw_response.users.get("user_id")
# response.http_response, response.parsed
# Stream large responses
stream = client.with_streaming_response.users.list()- Use
NotGivenfor truly optional parameters (vsNonefor nullable fields) - All request params use
TypedDictdefinitions - Pydantic v1/v2 compatible via
_compat.py - Always use types from
typing_extensions(Literal, TypedDict, etc.)
Client initialization:
from privy import PrivyAPI, AsyncPrivyAPI
# Production (default)
client = PrivyAPI(
app_id="your_app_id",
app_secret="your_app_secret"
)
# Staging
client = PrivyAPI(
app_id="...",
app_secret="...",
base_url=PrivyAPI.ENVIRONMENTS["staging"]
)
# With authorization key (for wallet operations)
client = PrivyAPI(
app_id="...",
authorization_key="auth_key_from_jwt_flow"
)Hybrid Public Key Encryption using P-256 + ChaCha20-Poly1305:
from privy.lib.hpke import generate_keypair, seal, open
# Generate keypair
keypair = generate_keypair()
# Returns: {"public_key": "base64...", "private_key": "base64..."}
# Encrypt
encrypted = seal(public_key=recipient_public_key, plaintext=b"message")
# Returns: {"ciphertext": "base64...", "encapsulated_key": "base64..."}
# Decrypt
decrypted = open(
private_key=private_key,
encapsulated_key=enc_key,
ciphertext=ciphertext
)
# Returns: {"message": "decrypted string"}Key formats: All keys are base64-encoded DER format (uncompressed P-256 points)
Request signing for API authentication:
from privy.lib.authorization_signatures import get_authorization_signature
signature = get_authorization_signature(
signing_key="base64_encoded_key",
request_method="POST",
request_path="/v1/users",
request_body={"key": "value"}
)Uses ECDSA P-256 + SHA-256 with canonical JSON serialization.
Simplified wallet creation with JWT authentication:
# lib/users.py:22
response = client.users.create_with_jwt_auth(
jwt_subject_id="user_jwt_subject",
wallets=[{"chain_type": "ethereum"}],
additional_linked_accounts=[{"type": "email", "address": "user@example.com"}]
)
# Returns: WalletCreateWalletsWithRecoveryResponseAutomatically:
- Sets up primary signer with JWT subject ID
- Creates recovery user with custom JWT account + additional accounts
- Creates specified wallet(s)
All-in-one authentication with automatic key decryption:
# lib/wallets.py:33
response = client.wallets.generate_user_signer(user_jwt="jwt_token")
# Access decrypted authorization key
auth_key = response.decrypted_authorization_key
expires_at = response.expires_at
wallets = response.walletsAutomatically:
- Generates ephemeral keypair
- Authenticates with JWT
- Decrypts authorization key via HPKE
- Returns ready-to-use authorization key
# Basic user creation (generated resource)
user = client.users.create(
linked_accounts=[
{"type": "custom_auth", "custom_user_id": "user_123"}
]
)
# With JWT auth and wallet (custom extension)
response = client.users.create_with_jwt_auth(
jwt_subject_id="jwt_subject_123",
wallets=[{"chain_type": "ethereum"}]
)# List user's wallets
wallets = client.wallets.list(user_id="did:privy:...")
# Get wallet balance
balance = client.wallets.balance.get(
wallet_address="0x...",
chain_type="ethereum"
)
# Create transaction (requires authorization key)
tx = client.wallets.transactions.create(
wallet_id="wallet_id",
chain_type="ethereum",
transaction={...}
)from privy import (
AuthenticationError,
NotFoundError,
RateLimitError,
APIStatusError
)
try:
user = client.users.get("user_id")
except NotFoundError:
# Handle 404
pass
except AuthenticationError:
# Handle 401
pass
except RateLimitError:
# Handle 429
pass
except APIStatusError as e:
# Generic HTTP error
print(f"Status: {e.status_code}, Body: {e.response.text}")- httpx (v0.23+) - Async HTTP client
- pydantic (v1/v2) - Data validation and serialization
- typing-extensions - Advanced type hints
- pyhpke - HPKE implementation (P-256 + ChaCha20-Poly1305)
- cryptography - ECDSA signing, key serialization
- anyio - Async compatibility
- distro - OS detection
When referencing code, use file:line format:
- Main client:
_client.py:1-100 - Custom users extension:
lib/users.py:22(create_with_jwt_auth) - Custom wallets extension:
lib/wallets.py:33(generate_user_signer) - HPKE encryption:
lib/hpke.py:1-124 - Base HTTP client:
_base_client.py:1-1500
No test configuration found in repository yet. When adding tests:
- Use pytest (standard for Python API clients)
- Test both sync and async variants
- Mock httpx requests for unit tests
- Integration tests should use staging environment
- Test cryptography functions with known test vectors
- Never modify generated files - Only extend via
lib/inheritance - Always provide sync + async variants for new methods
- Use NotGiven, not None for optional request parameters
- All keys are base64-encoded DER format (not PEM)
- HPKE uses P-256 curve (not secp256k1)
- Follow existing patterns in lib/ for custom extensions
- Pydantic v1/v2 compatible - use
_compat.pyshims - No setup.py/pyproject.toml - configuration likely in parent directory or not committed yet