Salesforce Client

The Salesforce Client is the main interface for interacting with the Salesforce API.

Client Types

There are two main client types:

  • SalesforceClient - Synchronous client

  • AsyncSalesforceClient - Asynchronous client for use with async/await

Basic Usage

from sf_toolkit import SalesforceClient, cli_login
from sf_toolkit.data import select
from sf_toolkit import SObject
from sf_toolkit.data.fields import IdField, TextField
# Define an SObject model (fields you care about)
class Account(SObject):
    Id = IdField()
    Name = TextField()

# Basic synchronous usage with SF CLI authentication (alias optional)
with SalesforceClient(login=cli_login()) as sf:
    # Synchronous properties (populated at login)
    print("Available API versions:", sf.versions)
    print("Current user Id:", sf._userinfo.user_id)

    # Raw API GET request (uses the auto-discovered data URL)
    account_describe = sf.get(f"{sf.sobjects_url}/Account/describe")
    print("Account label:", account_describe.json()["label"])

    # Build a SOQL query using the query builder (returns an iterator)
    accounts_iter = select(Account).where(Name__like="Acme%").limit(5).execute()

    # Iterate results (records loaded lazily)
    for acct in accounts_iter:
        print("Got account:", acct.Id, acct.Name)

    # You can also perform other CRUD operations, bulk operations, etc.
    # e.g. sf.post(sf.sobjects_url + "/Account", data={...})

# Client session is automatically closed when exiting the context manager

Asynchronous Usage

The asynchronous client mirrors the synchronous API but is designed for use with Python’s async / await syntax. Use AsyncSalesforceClient in an async context manager and await network-bound operations, including raw get/post calls and versions(). Unlike the synchronous client’s cached versions property, the async interface is await sf.versions(). Cached user info remains available through sf._userinfo after entering the context.

Minimal example:

import asyncio
from sf_toolkit import AsyncSalesforceClient, cli_login
from sf_toolkit.io import sobject_from_description

async def main():
    # Acquire an authenticated async client (SF CLI auth by default if no alias passed)
    async with AsyncSalesforceClient(login=cli_login()) as sf:
        print("API versions:", await sf.versions())
        print("User Id:", sf._userinfo.user_id)

        # Raw async GET request
        account_describe = await sf.get(f"{sf.sobjects_url}/Account/describe")
        print("Account label:", account_describe.json()["label"])

        # Example SOQL query using the high-level query builder (returns an async iterator)
        from sf_toolkit.data import select
        from sf_toolkit import SObject
        from sf_toolkit.data.fields import IdField, TextField

        class Account(SObject):
            Id = IdField()
            Name = TextField()

        # Build and execute query asynchronously
        accounts_iter = await select(Account).where(Name__like="Acme%").limit(5).execute_async()
        async for acct in accounts_iter:
            print("Got account:", acct.Id, acct.Name)

    # Client is automatically closed on exit

if __name__ == "__main__":
    asyncio.run(main())

Common async operations:

  • Raw requests: await sf.get(path), await sf.post(path, json=payload); decode with response.json()

  • SOQL via builder: await select(MyObject).where(…).execute_async()

  • Bulk / batch helpers expose *_async variants

  • Use async for to iterate result sets without loading all rows into memory

Tip: Keep long-running org interactions inside the async with block so the underlying HTTP session can be reused efficiently.

Authentication Methods

The client supports several authentication methods:

  • SF CLI authentication (cli_login)

  • OAuth Username/Password flow (password_login)

  • OAuth JWT Bearer flow (public_key_auth_login)

  • OAuth Client Credentials flow (client_credentials_flow_login)

  • SOAP Security Token authentication (security_token_login)

  • SOAP IP Filtering authentication (ip_filtering_non_service_login / ip_filtering_org_login)

  • Auto-selection via lazy_login

See the Authentication section for full details and examples.

Working with multiple connections

You can register and manage multiple connections to different Salesforce orgs:

# Create connections with different names
with (
    SalesforceClient(connection_name="production", login=cli_login("prod-org")) as prod,
    SalesforceClient(connection_name="sandbox", login=cli_login("sandbox-org")) as sandbox
):
        # Now you can use both clients
        prod_versions = prod.versions
        sandbox_versions = sandbox.versions

        # Later, you can retrieve connections by name
        prod_client = SalesforceClient.get_connection("production")
        sandbox_client = SalesforceClient.get_connection("sandbox")

Authenticated origins and request replay

Both clients restrict Salesforce bearer tokens to the session instance’s HTTPS origin (scheme, hostname and port). An unrelated absolute URL, HTTP downgrade, or embedded user information raises sf_toolkit.auth.SalesforceOriginError before the resource request is sent. OAuth/SOAP login exchanges use their own configured endpoints; those are not implicitly trusted resource destinations.

This is a security-related compatibility change: HTTP localhost/loopback mocks are also rejected, even when explicitly listed as trusted origins. For local tests use an in-memory HTTPX transport with an HTTPS instance URL, or an HTTPS mock with a trusted test certificate. Do not disable origin protection to send Salesforce credentials to an HTTP mock.

If an integration genuinely requires another resource origin, opt in explicitly:

client = SalesforceClient(
    login=login,
    allowed_resource_origins=["https://trusted-files.example.com"],
)

Every listed origin may receive the Salesforce token. Never populate this list from an untrusted pagination URL or use it as a general hostname allowlist. Requests with auth=None deliberately bypass Salesforce authentication and its origin check; protect any credentials you supply yourself.

The default retry transport retries transient failures for configured methods (GET, HEAD, OPTIONS, PUT, DELETE and PATCH). POST is excluded after an HTTP error or an ambiguous read/write/protocol failure. Connection-establishment and pool failures can still be retried for any method because no application request has been sent. A custom retry_methods set is an explicit opt-in to replay.

Use a per-request policy for operations where no replay is acceptable:

from sf_toolkit import NO_REPLAY, RequestPolicy

response = client.post(
    path,
    json=payload,
    extensions=NO_REPLAY.extensions,
)

# Independent control: permit transport retries but not expired-session replay.
policy = RequestPolicy(allow_auth_replay=False)
response = client.get(path, extensions=policy.extensions)

NO_REPLAY disables toolkit transport retries, authentication-refresh replay, and redirects, even if the client enables redirects. Initial login is still allowed before the resource request. Policies also work with build_request/ send and stream.

When an expired-session refresh returns a different HTTPS instance, a permitted replay aimed at the previous instance’s exact origin moves to the new instance, preserving path and query. Explicitly trusted alternate-origin requests stay on that alternate origin; arbitrary untrusted URLs are still rejected. An unsafe new instance (HTTP or user information in the URL) raises SalesforceOriginError without a replay. NO_REPLAY prevents refresh and retargeting as well as replay; it does not migrate an expired request.

When an in-flight replay moves to a new origin, its old Cookie header is discarded and toolkit clients select cookies from their jar for the new URL. Cookies restricted to the previous host are not copied to a different host; selection follows the cookie jar’s domain/path/security rules.

A request already created with build_request contains an absolute URL. If the instance changes before it is sent, the origin fence rejects that old-origin request. Rebuild it from the original relative URL/body with the same request policy. Do not trust historical origins or blindly redirect arbitrary prepared requests as a workaround.

Explicit session recovery

client.refresh_session() (await client.refresh_session() for async clients) performs fresh authentication and a read-only userinfo check. It does not resubmit any failed request or return an access token. It requires a login method; token-only clients must be replaced with fresh credentials instead. The existing session stays installed if renewal/verification fails, and a concurrent newer refresh is not overwritten.

Initial login, automatic refresh and explicit renewal share a revision-guarded session installation operation. Token/routing updates run together under a short state lock; network work runs outside it. User persistence callbacks run outside that state lock, with serialized notifications, and should be short/thread-safe. Do not change an active client’s auth/login configuration concurrently.

Explicit renewal parses the userinfo response and updates the cached user info. If an org/user identity is already known, it refuses to install a different one with SalesforceIdentityError. With no prior userinfo, the first verified renewal establishes the identity. Bind CLI authentication to an explicit alias or username and keep login configuration stable; automatic login flows trust that configuration. Create a separate client to intentionally change identities.

Use this after an expired-session failure from a NO_REPLAY operation, then deliberately retry reads or reconcile writes before another submission. Merely calling the same operation again with the expired cached token will fail again. Initial authentication and the verification read can use the normal authentication flow; the failed write remains untouched. Rebuild any unsent prepared requests after an instance-changing refresh.

These controls cannot prevent retries inside a custom transport, proxy, or remote service. A timeout/lost response after a write has an unknown outcome: read back and reconcile the state rather than treating it as proof of failure.

HTTPX configuration and transport ownership

Both clients forward HTTPX client options such as timeouts, redirects and event hooks. Their default retry transports also honor verify (including an exact ssl.SSLContext), cert, limits, trust_env, http1 and http2. HTTP/2 requires HTTPX’s optional HTTP/2 dependencies. Prefer loading client certificates into an SSL context; HTTPX deprecates its cert argument.

This is not full HTTPX transport parity. A supplied transport or mounts entry owns its TLS, pooling and retry settings; the toolkit does not rebuild or wrap it. HTTPX closes supplied transports when the client closes. Explicit proxy transports are constructed by HTTPX and do not inherit toolkit retry behavior. Because the toolkit supplies a retry transport, automatic environment proxy discovery is not enabled; configure proxy or mounts explicitly. trust_env still controls environment-based TLS trust configuration on the default retry transport and is retained as a client option.