Python SDK
vorlek is the Python 3.10+ client for the Vorlek API. It wraps the six tool endpoints with sync and async clients, generated OpenAPI result aliases, automatic ULID idempotency keys, and typed exceptions.
Quickstart
import os
from vorlek import VorlekClient
with VorlekClient(api_key=os.environ["VORLEK_API_KEY"]) as client:
result = client.contact.upsert(
provider="sendgrid",
email="jamie@example.com",
first_name="Jamie",
properties={"plan": "free"},
)
print(result.data["contact_id"])
print(result.meta.request_id)
import asyncio
import os
from vorlek import AsyncVorlekClient
async def main() -> None:
async with AsyncVorlekClient(api_key=os.environ["VORLEK_API_KEY"]) as client:
result = await client.connection.status(provider="sendgrid")
print(result.data["status"])
print(result.meta.request_id)
asyncio.run(main())
Method reference
Argument shapes are generated from the public OpenAPI schema at vorlek.dev/openapi.json. Providers are sendgrid, mailchimp, and klaviyo.
| Namespace | Method | Required arguments | Returns |
|---|---|---|---|
| contact | upsert(...) | provider, email | UpsertContactResult |
| connection | status(...) | provider | GetConnectionStatusResult |
| send | transactional(...) | provider, to, subject | SendTransactionalResult |
| campaign | stats(...) | provider, campaign_id | GetCampaignStatsResult |
| campaign | list(...) | provider | ListCampaignsResult |
| template | list(...) | provider | ListTemplatesResult |
| catalog | get() | none | CatalogResult |
| operations | get(request_id) | request_id | OperationLookupResult |
Catalog discovery
Call client.catalog.get() before planning a multi-provider workflow. The response tells agents which provider/tool cells are supported, which connection config is required, whether the current account has each provider connected, and that the current receipt lookup identifier is meta.request_id.
catalog = client.catalog.get()
for tool in catalog.data["tools"]:
print(tool["name"], tool["providers"])
Operation lookup
Every successful write returns meta.request_id. Treat that value as the operation receipt id and pass it to client.operations.get(request_id) when an agent needs same-surface observability without raw REST.
result = client.contact.upsert(provider="sendgrid", email="test@example.com")
operation = client.operations.get(result.meta.request_id)
print(operation.data["status"], operation.data["duration_ms"])
Contact readback
client.contact.get reads one contact by email so agents can verify an upsert_contact result without leaving the SDK surface.
readback = client.contact.get(provider="sendgrid", email="test@example.com")
print(readback.data["found"], readback.data["contact"])
Response shape
Successful calls return a frozen VorlekResult. data is the normalized tool payload. meta includes request_id, quota state, rate-limit state merged from X-RateLimit-* headers, and idempotency replay state when present.
result.data is parsed JSON: at runtime it is dict[str, object], even though the per-tool aliases (UpsertContactResult, SendTransactionalResult, and the rest) point at the generated dataclasses for type-checker convenience. Use subscript syntax — result.data["contact_id"], not result.data.contact_id.
Response detail
Tool methods accept detail="minimal", detail="standard", or detail="full". minimal returns only essential metadata, standard is the default, and full includes operation lookup metadata.
result = client.contact.upsert(
provider="sendgrid",
email="test@example.com",
detail="minimal",
)
result = client.template.list(provider="mailchimp", limit=10)
print(result.data["templates"])
print(result.meta.request_id)
print(result.meta.ratelimit.remaining if result.meta.ratelimit else None)
Template search
client.template.list accepts query for page-scoped template search across id, name, and subject. When the returned page has no match, inspect data["search"]["no_match"] for the next action.
result = client.template.list(provider="sendgrid", query="welcome")
print(result.data["templates"], result.data.get("search"))
Idempotency
Every tool call gets a fresh ULID idempotency key by default. Pass idempotency_key when retrying the same logical operation; the same key with the same request body is safe to call twice.
result = client.contact.upsert(
provider="mailchimp",
email="jamie@example.com",
idempotency_key="01HV0011V0110011V011001100",
)
print(result.meta.idempotency.replay if result.meta.idempotency else False)
from_ parameter
Python reserves from, so transactional sends use from_. The SDK serializes it as from before sending the request.
client.send.transactional(
provider="sendgrid",
to="jamie@example.com",
from_="updates@example.com",
subject="Welcome",
text="Thanks for trying Vorlek.",
)
Error classes
All API failures raise VorlekError subclasses. Check err.code, err.retry_safe, err.http_status, err.request_id, and err.provider. Network failures are SDK-synthesized and have http_status == 0.
| Class | Code | retry_safe | Use case |
|---|---|---|---|
VorlekAuthError | AUTH_MISSING, AUTH_INVALID, AUTH_FORBIDDEN | false | Missing, invalid, or under-scoped Vorlek API key. |
VorlekConnectionError | CONNECTION_NOT_FOUND, CONNECTION_INVALID | false | Provider is not connected or needs credential rotation. |
VorlekValidationError | INVALID_PARAMS, FIELD_TYPE_MISMATCH, PAYLOAD_TOO_LARGE | false | Request body or provider field type needs correction. |
VorlekToolNotSupportedError | TOOL_NOT_SUPPORTED | false | The selected provider/tool pair is intentionally unavailable. |
VorlekQuotaExceededError | QUOTA_EXCEEDED | true | Plan-period quota is exhausted; wait for reset or upgrade. |
VorlekRateLimitedError | RATE_LIMITED | true | Short-window throttling; honor Retry-After. |
VorlekIdempotencyConflictError | IDEMPOTENCY_CONFLICT | false | Same idempotency key was reused with a different request body. |
VorlekProviderAuthInvalidError | PROVIDER_AUTH_INVALID | false | Provider key is invalid, expired, or missing required scopes. |
VorlekProviderRateLimitedError | PROVIDER_RATE_LIMITED | true | Provider throttled the upstream call; retry with backoff. |
VorlekProviderUnavailableError | PROVIDER_UNAVAILABLE | true | Provider is temporarily unavailable. |
VorlekProviderFailedError | PROVIDER_FAILED | false | Provider reached a terminal failure; inspect provider detail. |
VorlekInternalError | INTERNAL_ERROR | true | Unexpected Vorlek-side failure; report the request id. |
VorlekConnectionDecryptFailedError | CONNECTION_DECRYPT_FAILED | false | Stored provider credential cannot be decrypted; reconnect provider. |
VorlekNetworkError | NETWORK_ERROR | true | Local network, DNS, TLS, timeout, or transport failure. |
from vorlek import VorlekClientError, VorlekRateLimitedError, is_retryable_error
try:
result = client.contact.upsert(provider="sendgrid", email="jamie@example.com")
except VorlekRateLimitedError as err:
schedule_retry(after_seconds=err.retry_after)
except VorlekClientError as err:
fix_request_or_connection(err.code)
except Exception as err:
if is_retryable_error(err):
retry_with_backoff()
Examples
contact.upsert- single contact upsert with result and meta output.send.transactional- minimal SendGrid transactional send usingfrom_.connection.statusplus tool calls - connect providers, then run status, upsert, templates, campaigns, and stats.
Test mode
vk_test_* keys run the same SDK methods against deterministic API fixtures. Use them for CI, retry-path tests, and agent evals; the full behavior is documented at vorlek.dev/docs/test-mode.
Cross-SDK parity
The TypeScript SDK is @vorlek/sdk. Both SDKs expose the same {data, meta} contract and six tool namespaces through registry-installed packages.