Skip to content

Python

Python SDK

Use the Python SDK when you want typed resource objects, synchronous or asynchronous calls, parsed provenance, and consistent error handling. The examples use the public v1 resource surface.

python -m pip install mpalo-sdk

Create a client

The constructor reads PALO_API_KEY when no key is supplied. It also resolves the configured base URL and validates key format. Pass an explicit key only when the value comes from a secure runtime source.

import os
from palo import Palo

client = Palo(
    api_key=os.environ["PALO_API_KEY"],
    timeout=30,
    max_retries=2,
)

Write, recall, and delete

The SDK validates non-empty text, namespace length, top-k bounds, and response contract fields before returning typed objects. Use a stable idempotency key for a logical event. Use namespaces to separate unrelated application contexts.

write = client.memory.write(
    "The user moved the appointment to Monday afternoon.",
    namespace="conversation",
    event_id="message_7",
    occurred_at="2026-08-28T10:00:00Z",
    idempotency_key="conversation-42-message-7",
    source={"type": "external_application", "id": "conversation-42-message-7"},
)
print(write.state)
print(write.decision)
print(write.source.type if write.source else None)
print(write.provenance.execution_mode)
print(write.usage.billing_state)

recall = client.memory.recall(
    "When is the current appointment?",
    namespace="conversation",
    top_k=3,
    as_of="2026-08-28T12:00:00Z",
)
print(recall.state, recall.proximity)

receipt = client.memory.delete(namespace="conversation")
print(receipt.state, receipt.deleted)

Import and migration

The current v1 API does not expose a separate bulk-import job. The Python package can parse, chunk, deduplicate, checkpoint, and estimate an archive locally. A committed migration sends each chunk through client.memory.write as an ordinary imported_archive event, so the active storage connection, consent policy, usage meter, billing ledger, provenance, and observability rules still apply.

for record in records:
    source_id = record["id"]
    write = client.memory.write(
        record["text"],
        namespace="imported",
        event_id=f"archive:{source_id}",
        idempotency_key=f"archive:{source_id}",
        occurred_at=record.get("occurred_at"),
        source={"type": "imported_archive", "id": source_id},
        metadata={"archive": "chatgpt-export-2026"},
    )
    print(write.state, write.decision, write.provenance)

The source object is caller-attested metadata. It does not verify who originally created the record. The API key determines the remote account and tenant scope. The local dry-run estimate is not an invoice, does not call an external model, and does not replace the usage and billing response from committed writes.

Infrastructure resources

Infrastructure methods return the deployment and measurement context that produced the response. Use them for startup checks, dashboards, incident investigation, and release verification. A health response is self-describing and includes provider, model version, deployment version, and environment identity. The SDK rejects a response that omits or malforms those fields.

capabilities = client.infrastructure.capabilities()
health = client.infrastructure.health()
status = client.infrastructure.status()
usage = client.infrastructure.usage(lookback_minutes=15)
events = client.infrastructure.events(limit=100, offset=0)

print(capabilities.operations)
print(health.deployment_version, health.environment)
print(status["monitoring"]["anomalies"])
print(usage["recorded_charge_usd"])
print(len(events["data"]))

The SDK also exposes metrics, anomalies, monitoring, and export. The async resource mirrors these methods.

Errors and safe retries

from palo import APIConnectionError, APITimeoutError, RateLimitError

try:
    result = client.memory.write(text, idempotency_key="event-42")
except (APITimeoutError, APIConnectionError):
    result = client.memory.write(text, idempotency_key="event-42")
except RateLimitError as error:
    print(error, error.retry_after)
Do not retry blindly.

A timeout means the server outcome may be unknown. Reuse the same idempotency key for a write and inspect the returned idempotency outcome.