Configure SDK for Python
Official Python SDK for Configure — persistent user memory and identity for AI agents.
PR 3.5 parity note: the Python SDK still exposes the legacy
/v1/memory/*surface and has not been fully updated for typed memory entry search/detail results. Use the TypeScript SDK or HTTP API for the PR 3.5 typed memory entry contract until Python parity is completed.
Installation
pip install configure-ai
Credentials and the OAuth callback
Credentials come from npx configure setup --users, which opens Configure developer auth once and writes all five values to .env: CONFIGURE_API_KEY, CONFIGURE_PUBLISHABLE_KEY, CONFIGURE_AGENT, CONFIGURE_OAUTH_CLIENT_ID, and CONFIGURE_OAUTH_CLIENT_SECRET.
Everything after that has a Python command:
python -m configure_ai verify # real sign-in, token exchange, and one live profile read
python -m configure_ai verify --offline # no browser: credentials, key, and exact callback registration
python -m configure_ai add-callback --framework fastapi # or flask, django
python -m configure_ai add-origin https://yourapp.com/auth/configure/callback
verify fails loudly on the mistakes that otherwise surface as an opaque OAuth error mid-integration: a callback that differs from the registration by a port or a trailing slash, a client secret that was reissued out from under a deploy, a publishable key pasted into CONFIGURE_API_KEY. It exits nonzero on any failure, so CI can gate on it.
add-callback writes the callback route, the client_secret_basic code exchange, and the sign-in button snippet for your framework, keeping the secret server-side. The generated browser page recovers the PKCE verifier when state is missing and finishes through Configure.completeSso() in a popup instead of navigating. It never overwrites an existing file unless you pass --force.
add-origin registers a deployed callback on the client in your .env. It opens the dashboard to confirm the exact client and callback, because an sk_ key cannot change an OAuth client, and prints the resulting callbacks. Registration is additive, so one CONFIGURE_OAUTH_CLIENT_ID covers local and production. The dashboard's Sign-in (SSO) page does the same thing by hand.
Quick Start
from configure_ai import ConfigureClient
# Initialize the client
client = ConfigureClient("sk_your_api_key")
# Authenticate user via OTP
client.auth.send_otp("+14155551234")
result = client.auth.verify_otp("+14155551234", "123456")
token = result.token
user_id = result.user_id
# Get user's profile
profile = client.profile.get(token, user_id)
print(f"User: {profile.get('user', {}).get('name', 'Unknown')}")
# Save a memory
client.profile.remember(token, user_id, "User's favorite color is blue")
# Close the client when done
client.close()
Using Context Manager
from configure_ai import ConfigureClient
with ConfigureClient("sk_your_api_key") as client:
client.auth.send_otp("+14155551234")
result = client.auth.verify_otp("+14155551234", "123456")
profile = client.profile.get(result.token, result.user_id)
Async Usage
import asyncio
from configure_ai import AsyncConfigureClient
async def main():
async with AsyncConfigureClient("sk_your_api_key") as client:
await client.auth.send_otp("+14155551234")
result = await client.auth.verify_otp("+14155551234", "123456")
profile = await client.profile.get(result.token, result.user_id)
await client.profile.remember(result.token, result.user_id, "User is vegetarian")
asyncio.run(main())
API-Only Unlinked Profiles
If your app already has stable user IDs, you can read and update profiles without hosted auth in the hot path. Pass user_id when constructing the server-side client with your sk_... key; the SDK sends it as X-User-Id.
from configure_ai import ConfigureClient
client = ConfigureClient(
"sk_your_api_key",
user_id="your-internal-user-id",
)
profile = client.profile.get()
client.profile.remember(fact="Prefers concise answers")
client.profile.ingest(
text="Known CRM or onboarding profile text",
sync=True,
)
This creates an unlinked developer-scoped profile. Other developers' agents cannot read it, and connected tools require the user to link later with hosted auth using the same external ID.
Message-Agent Line Registry
Message agents should register their current provider-owned return line before sending hosted sign-in.me links that include that phone.
from configure_ai import ConfigureClient
client = ConfigureClient("sk_your_api_key", agent="your-agent")
agent_phone = sms_provider.current_phone()
line = client.auth.register_message_line(
phone=agent_phone,
channel="sms",
label="Primary SMS line",
)
lines = client.auth.list_message_lines()
client.auth.revoke_message_line(phone=agent_phone, channel="sms")
Configure stores only a phone hash and last four digits. SDK results never include the raw phone number.
Tool Connections
Connect user accounts to access their data. Tool APIs require an agent-scoped token from hosted auth or trusted headless auth; unlinked user_id profiles can use profile APIs but cannot access connected tools until linked.
# List available tools
tools = client.tools.list(token)
for tool in tools.tools:
print(f"{tool.name}: {'Connected' if tool.connected else 'Not connected'}")
# Connect Gmail
result = client.tools.connect(token, "gmail", "https://myapp.com/callback")
print(f"Redirect user to: {result.auth_url}")
# After OAuth callback, confirm the connection
confirmation = client.tools.confirm(token, "gmail", result.connection_request_id)
# Search user's emails
emails = client.tools.search_emails(token, user_id, "from:boss@company.com")
for email in emails.emails:
print(f"- {email.subject}")
# Search every permitted Gmail and Outlook account
emails = client.tools.search_hosted_emails(token, user_id, "shipping update")
if emails.partial:
print("Some accounts could not be searched")
# Get calendar events
events = client.tools.get_calendar(token, user_id, "week")
for event in events.events:
print(f"- {event.summary} at {event.start}")
Memory Operations
# Get the full profile
profile = client.profile.get(token, user_id)
# Get a specific path
user_data = client.profile.get(token, user_id, path="user")
# Get agent-specific data
app_data = client.profile.get(token, user_id, sections=["agents"])
# Save a memory
client.profile.remember(token, user_id, "User's preferred language is Spanish")
# Ingest a message for memory extraction
from configure_ai import ConversationMessage
result = client.profile.ingest(
token,
user_id,
ConversationMessage(role="user", content="I always prefer aisle seats on flights"),
"Travel preferences, dietary restrictions"
)
if result.relevant:
print(f"Memories extracted: {result.memories_written}")
Profile Operations
Structured read/write access to profile data.
# Agent's own persistent storage
client.self.write("/soul.md", "I am TravelBot...")
soul = client.self.read("/soul.md")
listing = client.self.ls("/")
results = client.self.search("travel preferences")
# User's profile data (token-authenticated or constructor user_id)
summary = client.profile.read(token, user_id, "/summary.md")
client.profile.write(token, user_id, "/agents/travelbot/notes.md", "User prefers budget airlines")
# Peer agent profiles (read-only)
peer_soul = client.peer("wealthbot").read("/soul.md")
API Reference
ConfigureClient / AsyncConfigureClient
Main entry point for the SDK.
ConfigureClient(
api_key: str,
base_url: str = "https://api.configure.dev",
timeout: float = 30.0,
agent: str | None = None,
user_id: str | None = None
)
Modules
client.auth- Authentication (OTP flow)client.profile- Profile operations (get, remember, ingest, read, write, ls, search, rm)client.tools- Tool connections, search, and syncclient.self- Agent persistent storageclient.peer(name)- Peer agent data (read-only)
Auth Module
client.auth.send_otp(phone: str) -> OtpStartResponse
client.auth.verify_otp(phone: str, code: str) -> OtpVerifyResponse
Profile Module
client.profile.get(token, user_id, path=None) -> UserProfileResponse # .format() on response
client.profile.get_memories(token, user_id=None) -> MemoriesResponse
client.profile.remember(token, user_id, fact) -> RememberResponse
client.profile.ingest(token, user_id, messages, sync=True) -> IngestResponse
client.profile.read(token, user_id, path) -> dict | None
client.profile.write(token, user_id, path, content) -> dict
client.profile.ls(token, user_id, path="/") -> dict
client.profile.search(token, user_id, query) -> dict
client.profile.rm(token, user_id, path) -> dict
Tools Module
client.tools.list(token) -> ListToolsResponse
client.tools.connect(token, tool, callback_url=None) -> ConnectToolResponse
client.tools.confirm(token, tool, connection_request_id) -> ConfirmToolResponse
client.tools.sync(token, tool) -> SyncToolResponse
client.tools.disconnect(token, tool) -> None
client.tools.disconnect_all(token) -> None
client.tools.sync_all(token, user_id, tools=None) -> dict
client.tools.search_emails(token, user_id, query, max_results=10) -> SearchEmailsResponse
client.tools.get_calendar(token, user_id, range="week") -> SearchCalendarResponse
client.tools.search_files(token, user_id, query, max_results=10) -> SearchFilesResponse
client.tools.search_notes(token, user_id, query, max_results=10) -> SearchNotesResponse
Error Handling
All SDK methods raise ConfigureError with a typed code property and structured metadata:
from configure_ai import ConfigureError, classify_error
try:
profile = client.profile.get(token, user_id)
except ConfigureError as e:
if e.code == "AUTH_REQUIRED":
# Token expired or invalid — re-authenticate
# e.suggested_action == "reauthenticate"
pass
elif e.code == "RATE_LIMITED":
# Too many requests — back off and retry
# e.retryable == True, e.retry_after — seconds to wait
pass
elif e.code == "NETWORK_ERROR":
# Connection failed — check connectivity
# e.retryable == True
pass
else:
print(f"[{e.code}] {e}")
Errors include structured properties: e.type, e.param, e.retryable, e.suggested_action, e.doc_url, e.retry_after, e.request_id. Use e.retryable to determine if a retry is safe. Use classify_error(error) in agent except blocks to classify any error into a ConfigureError with a friendly message. See full error docs.
| Code | HTTP Status | Meaning |
|---|---|---|
API_KEY_MISSING |
— | No API key provided to constructor |
AUTH_REQUIRED |
401, 403 | Invalid or expired token |
INVALID_INPUT |
400 | Bad input (empty fields, path traversal) |
TOOL_NOT_CONNECTED |
400 | Tool action on a disconnected tool |
ACCESS_DENIED |
403 | Not authorized for this resource |
TOOL_ERROR |
varies | Tool operation failed (provider error) |
PAYMENT_REQUIRED |
402 | Billing/quota limit reached |
NOT_FOUND |
404 | Resource does not exist |
RATE_LIMITED |
429 | Too many requests |
SERVER_ERROR |
500+ | Server-side error |
NETWORK_ERROR |
— | Network/connection failure |
TIMEOUT |
— | Request timed out |
Requirements
- Python 3.8+
- httpx >= 0.24.0
License
Proprietary. All rights reserved. See configure.dev for licensing information.
Links
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file configure_ai-0.6.0.tar.gz.
File metadata
- Download URL: configure_ai-0.6.0.tar.gz
- Upload date:
- Size: 70.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b168c1b99a322f23a3fd081d253aa8c7e26b1ddc3639c152fd10dd1b28ad9044
|
|
| MD5 |
845ced9ea21169a7126d5ec662d524fb
|
|
| BLAKE2b-256 |
d323f75e93b66d70ac93d8c82c5c8238554a27bb1b99189e8648c960918dfcd8
|
File details
Details for the file configure_ai-0.6.0-py3-none-any.whl.
File metadata
- Download URL: configure_ai-0.6.0-py3-none-any.whl
- Upload date:
- Size: 58.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e7a8228bdb4bf43c718987524edc8a18a1c9828a994f01b87d98b34f2a8f46f3
|
|
| MD5 |
62efe3f3a8454343948116f841309278
|
|
| BLAKE2b-256 |
18612b63e64fbe36b998890adc5ffddc05a8f6b13f6f404f86c57be617854951
|