> For the complete documentation index, see [llms.txt](https://docs.soda.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.soda.io/reference/data-source-reference-for-soda-core/salesforce.md).

# Salesforce

Access configuration details to connect Soda to a Salesforce data source.

Soda works with Salesforce in two roles. You can **verify a contract against a Salesforce object**, and you can name a Salesforce dataset as the **source of a reconciliation contract** to compare warehouse data against the Salesforce records it was loaded from.

Salesforce answers SOQL rather than SQL, so support in both roles is partial. **A subset of contract checks and reconciliation checks cannot run against it**, and the values it returns are **compared with a tolerance**. A check Soda cannot run reports `NOT_EVALUATED` naming the reason, never a pass it did not measure. See the [caveats and limitations](#caveats-and-limitations) of this connection.

{% hint style="info" %}
To request access to the Salesforce data source for your organization, contact <support@soda.io> with your organization identifier.
{% endhint %}

## Connection configuration reference

Install the following package:

```bash
pip install soda-salesforce
```

{% hint style="info" %}
Contract verification against Salesforce needs **`soda-salesforce` 4.22.0 or later**. Earlier versions support reconciliation only.
{% endhint %}

**Create the config file:**

```shellscript
soda data-source create -f ds_config.yml
```

The data source configuration YAML should look like the following:

{% code title="ds\_config.yml" %}

```yaml
type: salesforce
name: my_salesforce
connection:
  authentication: client_credentials
  domain: https://acme.my.salesforce.com
  consumer_key: ${env.SF_CONSUMER_KEY}
  consumer_secret: ${env.SF_CONSUMER_SECRET}
  api_version: "60.0"
  timeout_seconds: 120
```

{% endcode %}

### Connection properties

<table><thead><tr><th width="146">Property</th><th width="96">Required</th><th width="104">Type</th><th width="96">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>type</code></td><td>yes</td><td>string</td><td>—</td><td>Identify the type of data source for Soda. In this case, must be <code>salesforce</code>.</td></tr><tr><td><code>authentication</code></td><td>no</td><td>string</td><td><code>client_credentials</code></td><td>The authentication flow. <code>client_credentials</code> is the only supported value.</td></tr><tr><td><code>domain</code></td><td>yes</td><td>string</td><td>—</td><td>The My Domain host of your Salesforce org, for example <code>acme.my.salesforce.com</code>. Soda adds <code>https://</code> when you omit it and tolerates a trailing slash. An explicit <code>http://</code> is rejected, because the token request carries the consumer secret.</td></tr><tr><td><code>consumer_key</code></td><td>yes</td><td>string</td><td>—</td><td>The consumer key of the Connected App. Consider using an environment variable to retrieve this value securely, for example <code>${env.SF_CONSUMER_KEY}</code>.</td></tr><tr><td><code>consumer_secret</code></td><td>yes</td><td>string</td><td>—</td><td>The consumer secret of the Connected App. Consider using an environment variable to retrieve this value securely, for example <code>${env.SF_CONSUMER_SECRET}</code>.</td></tr><tr><td><code>api_version</code></td><td>no</td><td>string</td><td><code>60.0</code></td><td>The Salesforce REST API version to pin, in <code>major.minor</code> form. A value with no minor component, such as <code>60</code>, is rejected. Quote the value so YAML reads it as a string.</td></tr><tr><td><code>timeout_seconds</code></td><td>no</td><td>integer</td><td><code>300</code></td><td>The request timeout in seconds.</td></tr></tbody></table>

### Salesforce prerequisites

Provision a **Connected App with the OAuth 2.0 client credentials flow enabled**. No other flow is supported.

Designate a **Run As user for the Connected App**, with **API access** and **read permission** on the objects and fields you want to read. Every query Soda issues runs as that user, so the Run As user's object-level and field-level security bounds what Soda can read. Grant nothing beyond read.

Soda reads and never writes. Against your org it issues SOQL `SELECT` queries and metadata calls, nothing else.

## Connection test

Test the data source connection:

```bash
soda data-source test -ds ds_config.yml
```

## Object and field names

Refer to objects and fields by their Salesforce API names, in the casing Salesforce reports.

* A dataset is `<data_source_name>/<ObjectApiName>`. Standard objects carry no suffix, as in `my_salesforce/Account`; custom objects end in `__c`, as in `my_salesforce/Invoice__c`.
* A column is a field API name. Standard fields are PascalCase, such as `Id`, `Name` or `CreatedDate`; custom fields end in `__c`, such as `Region__c`.
* `Id` is the natural key of every object. It is always present, always sortable, and always 18 characters.

To find an object's field API names, go to **Setup > Object Manager** in Salesforce.

## Contract verification support

Write the contract against the Salesforce dataset and verify it the same way you verify any other contract. There is no Salesforce-specific contract syntax.

### Supported contract checks

| Check         | Salesforce        | Notes                                                                                                                                  |
| ------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `row_count`   | Supported         |                                                                                                                                        |
| `schema`      | Supported         | Set `allow_extra_columns` and `allow_other_column_order`. See [#schema-checks](#schema-checks "mention").                              |
| `missing`     | Supported         | Both `missing_count` and `missing_percent`.                                                                                            |
| `invalid`     | Partly supported  | `valid_values`, `invalid_values`, `valid_min` and `valid_max` are supported. Regex, format, length and `valid_reference_data` are not. |
| `duplicate`   | Column level only | A dataset-level `duplicate` check identifies rows by a row hash, which SOQL cannot express.                                            |
| `aggregate`   | Partly supported  | `sum`, `min`, `max` and `avg` are supported. The length aggregates are not.                                                            |
| `freshness`   | Supported         |                                                                                                                                        |
| `metric`      | Supported         | Both the expression form and `query`. Write the query in SOQL.                                                                         |
| `failed_rows` | Supported         | `expression`, `query` and `keys_query` all evaluate. Write `query` and `keys_query` in SOQL.                                           |
| `group_by`    | Not supported     | The check runs a grouped query the org cannot answer.                                                                                  |

### Writing checks against Salesforce

SOQL is strict about literals where SQL is not. A quoted value against a numeric field and a bare number against a text field are each rejected outright, where a warehouse would cast silently. Soda reads the object's metadata and writes each literal the way the field's type requires, so you can use whichever YAML form is natural:

{% code title="contract.yml" %}

```yaml
columns:
  - name: Amount__c                        # a Number field
    valid_values: ["0", "10"]              # YAML strings, sent unquoted
  - name: Postcode__c                      # a Text field
    valid_values: [1000, 2000]             # YAML numbers, sent quoted
  - name: CloseDate__c                     # a Date field
    valid_values: ["2026-01-01"]
  - name: CreatedDate                      # a DateTime field
    valid_values: ["2026-01-01T00:00:00Z"]
```

{% endcode %}

**A Date field and a DateTime field take different literals, and SOQL accepts neither in the other's place.** A Date field takes `2026-01-01`; a DateTime field takes `2026-01-01T00:00:00Z` and must carry a timezone. Writing a bare date against a DateTime field reports `NOT_EVALUATED` naming the form the field needs, rather than assuming a time and a zone on your behalf.

Filters become a SOQL `WHERE` clause, so they must use predicates SOQL can express on field API names: the comparison operators, `AND`, `OR`, `LIKE`, `IN` over literals, and `IS NULL`.

{% hint style="warning" %}
**SOQL negation includes nulls.** `!=` and `NOT IN` match rows where the field is null, where SQL's three-valued logic excludes them. A filter written to exclude a set of values keeps the null rows on Salesforce and drops them on a warehouse, so add an explicit null condition when a check has to agree across both.
{% endhint %}

A `metric` or `failed_rows` check written with `query` sends your query to the org exactly as you wrote it. Write it in SOQL, and name the object rather than the Soda dataset:

{% code title="contract.yml" %}

```yaml
checks:
  - failed_rows:
      name: no invoices above the approval ceiling
      query: |
        SELECT Id, Amount__c FROM Invoice__c WHERE Amount__c > 100000
```

{% endcode %}

A `filter` on a `metric` check does not narrow what the metric measures, on Salesforce or on any other data source. Put the condition in the expression or the query.

### Schema checks

A schema check compares names, missing columns, type names, string length, and numeric precision and scale. Three things about Salesforce metadata shape how you write one:

* Salesforce reports the standard fields on every object, among them `Id`, `Name`, `CreatedDate`, `IsDeleted` and `SystemModstamp`. Either declare them or set `allow_extra_columns: true`.
* Column order is Salesforce's, not the contract's, so set `allow_other_column_order: true`.
* Every Number field reports as a double whatever its precision and scale, and no field reports a datetime precision. Declaring `datetime_precision` is rejected when the contract is parsed.

### Checks that cannot run

A check Soda cannot run against Salesforce reports `NOT_EVALUATED` with the reason, never a result it did not measure. Where the refusal happens decides what else it costs.

* **Refused when the contract is parsed:** a check type Salesforce does not support, and a dataset-level `duplicate`. No query is built and the rest of the contract runs normally.
* **Refused when the query runs:** a regex, a format, or a length validity rule. Soda batches aggregation checks into shared queries, so the refusal fails the whole batch and every check batched with it reports `NOT_EVALUATED`, not only the offending one. Keep these rules out of a contract you want partial results from.

SOQL has no length function, so the length aggregates `min_length`, `max_length` and `avg_length` cannot be measured either. Leave them out of a Salesforce contract.

{% hint style="warning" %}
**Row sampling is refused for the whole dataset.** SOQL has no equivalent of `TABLESAMPLE`, so a verification that requests sampled rows un-evaluates every check on that dataset, including the checks that did not ask for a sample.
{% endhint %}

### Failing rows

Soda stores the rows behind a failing check in a [Diagnostics Warehouse](broken://pages/UZCAHd8ZBgl01336NkZa). Salesforce cannot host one, because that needs write access to the org, so point the warehouse at a separate Postgres or Databricks data source and Soda extracts the rows across.

Rows are keyed by the Salesforce record `Id` rather than by a hash of the check's columns.

Two checks evaluate normally but have no rows to store:

* A column-level `duplicate` check. Identifying which records duplicated needs a grouped query joined back to the source rows, which SOQL cannot express in a single statement.
* A `failed_rows` check written with `keys_query`. Soda wraps the query in a common table expression, which SOQL has no form for.

A `failed_rows` check written with `query` stores the result of your query in its own table, where every column is typed as text.

Without a Diagnostics Warehouse, a failing check on Salesforce reports its count and no rows.

## Reconciliation support

Define the contract on the warehouse dataset and name the Salesforce dataset as the source. Reconciliation runs through the same engine it uses for every other data source. There is no Salesforce-specific check logic.

> For the syntax, see the [reconciliation checks reference](/reference/contract-language-reference/reconciliation-checks.md).

### Supported checks

| Check                                     | Salesforce    | Notes                                                                                    |
| ----------------------------------------- | ------------- | ---------------------------------------------------------------------------------------- |
| `row_count_diff`                          | Supported     | `COUNT` is pushed down to SOQL.                                                          |
| `aggregate_diff` with `sum`, `min`, `max` | Supported     | Values compare exactly.                                                                  |
| `aggregate_diff` with `avg`               | Supported     | Set a tolerance threshold. See [#comparison-tolerance](#comparison-tolerance "mention"). |
| `metric_diff`                             | Supported     | Both the expression form and `source_query`.                                             |
| `freshness_diff`                          | Supported     |                                                                                          |
| `rows_diff`                               | Supported     | Requires explicit `source_columns` and `target_columns`.                                 |
| `reference_diff`                          | Supported     | Requires explicit columns on both sides.                                                 |
| `duplicate_diff`                          | Not supported | SOQL has no per-row hash.                                                                |

### Key columns and ordering

Row-level reconciliation reads each side ordered by key, then merges the two streams. The key columns have to be orderable in SOQL, which constrains what you can key on.

Ordering is ascending only. Soda rejects a descending order. SOQL also cannot order by a long text field, so Soda validates the key columns before it opens a cursor and names any column it cannot use, rather than failing part way through a read.

SOQL sorts `NULL` first. Keep reconciliation keys non-null.

`Id` is the safest key. It is always present, always sortable, and immune to the text-ordering problem described under [#comparison-tolerance](#comparison-tolerance "mention").

### Column names

A Salesforce custom field carries an API name with a `__c` suffix. Soda does not map those onto warehouse column names for you, so name the columns explicitly on each side of every check.

The same asymmetry affects filters. A check-level `filter` applies one predicate to both sides, and one predicate cannot name `region__c` and `region` at once. Put the Salesforce predicate in a source-side filter instead. Any two data sources with differing column names have this problem; Salesforce just guarantees it.

### Query behavior

Every reconciliation metric compiles to SOQL. Aggregates are evaluated inside your org and only the resulting value comes back. Row-level checks read both sides ordered by key and stream the results in pages, so a large comparison is not bound by the SOQL offset ceiling.

Where several metrics cover one object and share a filter, Soda compiles them into as few SOQL statements as it can. Thirty metrics on one object cost a handful of calls.

A transient failure mid-stream, such as a rate limit or a gateway error, is retried on the same page.

## Availability in Soda Cloud

Onboard a Salesforce data source and its datasets the same way you onboard any other, then author and schedule contracts against them.

These parts of Soda Cloud are unavailable for a Salesforce data source:

* **Dataset profiling.** Onboard with profiling turned off.
* **Metric Monitoring**, and the anomaly detection built on it. Onboard with monitoring turned off.
* **Contract Autopilot**, which generates a contract from profiled statistics.
* **Data standards.** Salesforce datasets are left out of a standard's scope rather than failing when the standard runs.
* **Sample data**, which rests on the row sampling Salesforce refuses.

## Caveats and limitations

### Unsupported reconciliation checks and filters

* `duplicate_diff` always reports `NOT_EVALUATED`, because SOQL cannot compute a per-row hash.
* A `rows_diff` with no value columns reports `NOT_EVALUATED`. SOQL has no `SELECT *`, so set `source_columns` and `target_columns`. A `reference_diff` that omits `columns`, or uses the shorthand form, does the same, since Salesforce field names do not resolve to warehouse column names automatically.
* Two kinds of filter also report `NOT_EVALUATED`: a predicate SOQL cannot express, such as a bare literal like `1 = 1` or a SQL-only function, and a datetime literal with no timezone. SOQL datetime literals must carry a timezone, and Soda does not guess one.
* Any reconciliation check that asks for row sampling reports `NOT_EVALUATED`. SOQL has no equivalent of `TABLESAMPLE`.

### Comparison tolerance

* Salesforce returns numbers over JSON and datetimes as timezone-aware values. Soda therefore **compares values from a Salesforce source with a tolerance** rather than testing exact equality.
* Fractional numbers that differ below a relative tolerance of `1e-12` compare as equal. Integral values compare exactly. Datetimes compare to microsecond precision, and anything finer is truncated. Two warehouse data sources would flag differences that a Salesforce reconciliation treats as equal.
* This is why `aggregate_diff` on `avg` needs a tolerance threshold. An exact `must_be: 0` can fail on representation differences alone, with no discrepancy in the underlying data.
* Text is compared case-sensitively, so a difference in case is a real difference and Soda reports it as one. Ordering is the complication: SOQL sorts text case-insensitively while a warehouse sorts by codepoint, so the two streams can disagree on order. For `rows_diff` this matters only when one dataset holds two keys differing solely in case. `reference_diff` is more exposed, because any mixed-case text can push its count high and report added and removed rows for two sets that match. Non-ASCII text keys carry the same risk in both checks.
* Uniform-case keys are exact. `Id` keys are exact.

### API request limits

Your org's API request limits apply to every call Soda makes. A large contract or reconciliation costs a metadata call per object, plus a call per page of rows and per aggregate, so it can take a real share of the daily quota.

{% hint style="warning" %}
**Checks affected by an exhausted quota report `NOT_EVALUATED`**, not a failure. A scan can therefore under-report rather than turn red, so treat a run with unevaluated checks as incomplete.
{% endhint %}

Filter each check on an indexed field such as `LastModifiedDate` or `SystemModstamp` to scope the comparison to a recent window instead of the whole object. Schedule scans outside your peak hours.

***

{% hint style="info" %}
You are **not logged in to Soda** and are viewing the default public documentation. Learn more about [Licensing & documentation access](/reference/documentation-access-and-licensing.md).

If you do have a Soda license, make sure to **log in to Soda Cloud in this same browser**.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.soda.io/reference/data-source-reference-for-soda-core/salesforce.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
