# simple-salesforce: A Low-Level Python Client for Salesforce REST

> simple-salesforce gives Python applications direct access to Salesforce REST resources and APEX as decoded API dictionaries. It suits developers who want Salesforce object operations without a higher-level application framework.

**simple-salesforce/simple-salesforce** — A very simple Salesforce.com REST API client for Python

- Repository: https://github.com/simple-salesforce/simple-salesforce
- Stars: 1,897 · Forks: 724
- Language: Python
- License: NOASSERTION
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/simple-salesforce-simple-salesforce

## Why simple-salesforce Exposes Salesforce as Raw Responses

simple-salesforce addresses developers who need to call Salesforce from Python without introducing a separate object mapper or application framework. The README describes the package as a low-level interface to Salesforce REST resources and the APEX API. Its result contract is a Python dictionary containing the decoded JSON response from Salesforce. That choice keeps the client close to Salesforce's API and makes it usable for scripts, internal services, and applications that already understand Salesforce field names.

The client is not limited to Contacts. Salesforce resources are exposed through objects such as `sf.Contact`, while the package also provides access to REST resources and APEX. This makes it suitable for jobs that combine ordinary record handling with Salesforce-specific objects, custom fields, or APEX operations. It is a better fit when the application needs precise control over requests than when developers want an ORM-like abstraction that hides request construction.

There is a tradeoff in that low-level design. The README does not document automatic retry behavior, a local object cache, model validation, background synchronization, or a separate error model beyond returning Salesforce responses. Applications should therefore expect to handle the business meaning of API results themselves. The package also depends on Salesforce credentials and permissions, so installing the Python library does not eliminate the need to configure a Salesforce user, connected app, token, or approved authentication flow.

## Authentication Paths and Their Salesforce Constraints

The documented authentication choices cover direct access with an existing session, username and password with a security token, organization ID plus password authentication for IP filtering, JWT bearer authentication, and connected-app credentials. The exact parameters depend on the method. A caller that already has an instance domain and access token can construct the client with `instance` and `session_id`. A full instance URL can instead be passed through `instance_url`.

The JWT method is intended for a server-side integration that has a username, a consumer key, and a private-key file. The connected-app method uses `consumer_key` and `consumer_secret`; the README also notes that a client ID and client secret may be configured under those names, together with `domain`. Sandbox access is selected with `domain='test'`. These are Salesforce configuration choices, not alternate local modes supplied by the package.

The README lists several authentication categories but does not provide a complete setup guide for each Salesforce administrator task. It does not document token storage, refresh behavior, key rotation, or the permissions required by a particular operation. The example values also place credentials directly in source code, which is convenient for showing parameters but not a production secret-management policy. The package accepts a `requests.Session`, allowing the caller to apply specialized session handling, but the README does not define a retry or refresh configuration. Authentication should therefore be selected from the connected application's Salesforce configuration rather than inferred from the Python client alone.

## Record Operations Follow Salesforce Object Names

The primary data path is direct mapping between Python calls and Salesforce resource operations. Creating a Contact passes a dictionary containing Salesforce fields and returns the Salesforce response, including an `id` when creation succeeds. The README shows `sf.Contact.create({'LastName':'Smith','Email':'example@example.com'})`, followed by retrieval with `sf.Contact.get('003e0000003GuNXAA0')`. This keeps the wire representation visible in application code.

The package supports operations for reading, updating, deleting, and retrieving deleted or recently updated records. It also exposes `get_by_custom_id`, which uses a field defined as External ID. The update example passes the record identifier and a dictionary of field values, while the delete example passes only the identifier. For deleted and updated records, the README requires datetimes in UTC, so callers must normalize time zones before invoking those methods.

The API response remains the source of truth. The create example includes `errors`, `id`, and `success`, rather than a custom result object that hides those fields. That is useful for applications that need Salesforce's detailed status information, but it also means application code must inspect the returned dictionary instead of assuming that a Python exception represents every rejected operation. The README does not provide a complete error-handling policy or a schema for every Salesforce resource.

## Session Injection and APEX Access Expand the HTTP Boundary

The constructor accepts an instance of `requests.Session`. According to the README, this exists so applications can use specialized session handling that the client does not expose directly. The documented example creates a session, optionally manipulates it, and passes it as `session`. This gives Python code a place to configure application-level HTTP behavior without replacing the Salesforce client.

The README states that the package accesses both the REST Resource and APEX APIs, but the visible examples concentrate on REST record management. It does not show an APEX call, its response shape, or a separate APEX-specific authentication path. Consequently, an application should consult the Salesforce API documentation and the Simple Salesforce documentation for the exact APEX method signature rather than assuming that REST record syntax applies unchanged.

The same low-level boundary affects transport features. `requests` is a declared dependency, but the README does not claim that retries, circuit breaking, request correlation, or idempotency keys are handled by `simple-salesforce`. Those concerns may belong in the injected session or in the surrounding service. This division is useful for small integrations, although it means the package is not a complete policy layer for long-running Salesforce traffic.

## Installing simple-salesforce and Making a First Record Request

The repository uses standard Python packaging metadata in `pyproject.toml`, with `setup.py` retained as a minimal setuptools shim. The declared package name is `simple-salesforce`, Python support starts at 3.9, and the runtime dependencies are `requests`, `typing-extensions`, `zeep`, `pyjwt[crypto]`, and `more-itertools`. The README does not give an exact installation command, so the package manager command is left unspecified here rather than guessed.

Once the package is installed in a Python environment, a first request can use an existing Salesforce instance and access token. The following example is copied from the README. Supplying a real domain and token establishes the client for subsequent resource calls.

```python
from simple_salesforce import Salesforce
sf = Salesforce(instance='na1.salesforce.com', session_id='')
```

A real use can then create a Contact. The response should contain the Salesforce operation result rather than a locally generated record model.

```python
sf.Contact.create({'LastName':'Smith','Email':'example@example.com'})
```

The repository metadata does not document a first-use setup sequence for creating credentials, selecting permissions, or validating access. Those steps belong to the Salesforce organization. The `client_id` parameter can make calls easier to identify, and the README says Salesforce prefixes the displayed client identifier with `simple-salesforce/`.

## License Metadata and Python Version Boundaries

The repository contains both `LICENSE.txt` and package metadata that can be read differently during adoption review. `pyproject.toml` declares `Apache-2.0`, while the repository classification identifies the license as a custom license that GitHub cannot classify. The actual `LICENSE.txt` file is therefore the first file to inspect when license obligations matter. This article does not interpret the legal terms or provide legal advice.

The package requires Python 3.9 or later. Its classifiers list Python 3.9 through 3.14 and PyPy. Recent release information lists Python 3.14 support in v1.12.10, along with an authentication exception fix and PyPI trusted publishing. The release history also includes v1.12.9 on 2025-08-23 and v1.12.8 on 2025-08-21. The repository's last push was on 2026-07-08, so the project has recent recorded development, but this does not by itself describe the support policy for every Salesforce API change.

Upgrade cost is not limited to changing the package version. A deployment must account for Python compatibility, dependency resolution, Salesforce authentication configuration, and regression testing against the specific records and APEX endpoints used by the application. The README does not document a compatibility matrix for Salesforce editions or a formal deprecation policy.

## When the Raw Salesforce Client Is the Wrong Choice

simple-salesforce is a poor fit when the application expects a full CRM integration layer with managed synchronization, local caching, field-level validation, background jobs, or a stable domain model independent of Salesforce objects. The project's stated goal is a basic client, and its documented outputs are API dictionaries. Adding those responsibilities to the calling application can increase maintenance work for long-lived services.

It is also the wrong choice for a workflow that requires offline Salesforce access but has no designed cache or retry layer. The README does not claim that records are stored locally or that failed requests are automatically retried. A deployment that cannot tolerate Salesforce response latency or transient network failures needs those behaviors designed outside the package and verified with the chosen `requests.Session` or surrounding service.

A direct Salesforce API client is preferable when exact API visibility matters, existing Python code already uses Salesforce field names, or the application needs a small integration surface. The key decision is whether raw dictionary responses are acceptable. If they are not, the project lacks the higher-level abstractions that would make it a better fit.

## Conclusion

Adopt simple-salesforce for Python services that need direct Salesforce REST and APEX access, especially when dictionary-based responses fit the application. Do not choose it for a larger CRM framework with its own object model, buffering, retry policy, or managed integration layer. Review LICENSE.txt first, then verify the authentication method, Salesforce permissions, session handling, and response behavior against the intended production calls.

## FAQ

### How do I authenticate to Salesforce with simple-salesforce?

The documented methods include passing an existing instance and session token, username/password with a security token, organization ID authentication, JWT signing with a private key file, and connected-app credentials. The README provides separate constructor examples for these approaches.

### How do I create, update, retrieve, and delete a Salesforce record with simple-salesforce?

Use the resource-specific client, such as `sf.Contact`, with `create`, `get`, `update`, or `delete`. Calls accept Python dictionaries containing Salesforce field names and return the API JSON response as a dictionary.

### Does simple-salesforce support sandbox Salesforce instances?

The README says to add `domain='test'` to the `Salesforce()` call when entering a sandbox. It also notes that the domain parameter is relevant to built-in username, password, and security-token authentication during authentication.

## Sources

- [Issues](https://github.com/simple-salesforce/simple-salesforce/issues)
- [README](https://github.com/simple-salesforce/simple-salesforce/blob/master/README.md)
- [Releases](https://github.com/simple-salesforce/simple-salesforce/releases)
- [simple-salesforce/simple-salesforce on GitHub](https://github.com/simple-salesforce/simple-salesforce)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/simple-salesforce-simple-salesforce
