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 of this connection.
Connection configuration reference
Install the following package:
pip install soda-salesforceCreate the config file:
soda data-source create -f ds_config.ymlThe data source configuration YAML should look like the following:
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: 120Connection properties
type
yes
string
—
Identify the type of data source for Soda. In this case, must be salesforce.
authentication
no
string
client_credentials
The authentication flow. client_credentials is the only supported value.
domain
yes
string
—
The My Domain host of your Salesforce org, for example acme.my.salesforce.com. Soda adds https:// when you omit it and tolerates a trailing slash. An explicit http:// is rejected, because the token request carries the consumer secret.
consumer_key
yes
string
—
The consumer key of the Connected App. Consider using an environment variable to retrieve this value securely, for example ${env.SF_CONSUMER_KEY}.
consumer_secret
yes
string
—
The consumer secret of the Connected App. Consider using an environment variable to retrieve this value securely, for example ${env.SF_CONSUMER_SECRET}.
api_version
no
string
60.0
The Salesforce REST API version to pin, in major.minor form. A value with no minor component, such as 60, is rejected. Quote the value so YAML reads it as a string.
timeout_seconds
no
integer
300
The request timeout in seconds.
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:
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 inmy_salesforce/Account; custom objects end in__c, as inmy_salesforce/Invoice__c.A column is a field API name. Standard fields are PascalCase, such as
Id,NameorCreatedDate; custom fields end in__c, such asRegion__c.Idis 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
row_count
Supported
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:
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.
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.
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:
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,IsDeletedandSystemModstamp. Either declare them or setallow_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_precisionis 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.
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.
Failing rows
Soda stores the rows behind a failing check in a Diagnostics Warehouse. 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
duplicatecheck. Identifying which records duplicated needs a grouped query joined back to the source rows, which SOQL cannot express in a single statement.A
failed_rowscheck written withkeys_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.
Supported checks
row_count_diff
Supported
COUNT is pushed down to SOQL.
aggregate_diff with sum, min, max
Supported
Values compare exactly.
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.
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_diffalways reportsNOT_EVALUATED, because SOQL cannot compute a per-row hash.A
rows_diffwith no value columns reportsNOT_EVALUATED. SOQL has noSELECT *, so setsource_columnsandtarget_columns. Areference_diffthat omitscolumns, 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 like1 = 1or 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 ofTABLESAMPLE.
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-12compare 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_diffonavgneeds a tolerance threshold. An exactmust_be: 0can 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_diffthis matters only when one dataset holds two keys differing solely in case.reference_diffis 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.
Idkeys 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.
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.
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.
Last updated
Was this helpful?
