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 clientAsyncSalesforceClient- Asynchronous client for use withasync/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
*_asyncvariantsUse 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.