Evaluating CodeRabbit? Same review, plus real test runs. See why

API Testing12 min read

Salesforce API Testing: REST, SOAP, Bulk, Pub/Sub and GraphQL

S
Technical Writer, Qodex
The Salesforce cloud logo on a plain background
Part of our API Testing guide. Read the guide

Salesforce API testing means calling an org's REST, SOAP, Bulk 2.0, Pub/Sub or GraphQL API and checking what comes back: the status, the fields, the records changed, the errors and the request limits. It runs against a sandbox or scratch org with an OAuth token, not production, and it is separate from Apex unit tests and UI tests.

If you want Salesforce API tests without writing them by hand, Qodex generates them from a sentence and reruns them on every pull request.

The Salesforce APIs you will test, and the thing that breaks first with each.

APIWhat it is forAuthThe limit that bites first
REST API (v67.0)Synchronous CRUD, SOQL queries, metadata discoveryToken in the Authorization: Bearer headerThe rolling 24-hour call pool, shared with SOAP and Bulk (REQUEST_LIMIT_EXCEEDED)
SOAP API (v67.0 enterprise or partner WSDL)Contract-led integrations needing a typed schemaToken in the sessionId header elementThe same pool. SOAP login() is gone from v65.0 up, and retires in versions 31.0 to 64.0 in Summer '27
Bulk API 2.0 (v67.0)Asynchronous insert, upsert, update, delete and query jobsThe same tokenThe same pool. Failed rows are reported apart from the job state
Pub/Sub API (the eventbus.v1 gRPC service)Platform events, change data capture, event monitoring, in Avro over HTTP/2The same tokenNot a pool number: replay IDs, where a subscriber misses or duplicates events
Streaming API (legacy)Older event streaming, still a listed way to subscribeThe same tokenBuild nothing new on it. Pub/Sub is the current interface
GraphQL API (/services/data/v67.0/graphql)Related records in one request, only the fields you nameThe same tokenThe schema follows the caller's permissions

Versions and endpoints come from Salesforce's Summer '26 developer guide and the per-API guides, read on 2 September 2026. If the choice of request style is still open, start with SOAP vs REST API.

What is Salesforce API testing?

Salesforce API testing is one branch of API testing, applied to a platform where the caller's permissions change the answer. Three things it is not. Apex unit tests run inside the org against Apex code. UI tests drive Lightning pages in a browser. The Agentforce Testing API tests agents, a different product, out of scope here.

What it is: remote calls against a real org, with the real integration user's credentials, asserting on the HTTP status, the response body, the records that changed, the error codes and the request allocation consumed. That last item is the one teams forget. A test that passes while burning more of the daily allocation than your budget allows is not a passing test.

Which Salesforce API should you test?

Pick the API by the job, not by habit. REST is the default for CRUD and SOQL queries. Bulk API 2.0 handles asynchronous jobs, and Salesforce says operations above 2,000 records are good Bulk API 2.0 candidates, which is the switchover point to work from. SOAP stays where the consumer needs a WSDL contract. Pub/Sub covers events, and Streaming only maintains subscriptions already running on it. GraphQL earns its place when one request must fetch related records and return only the fields you name.

Then pin a version. Summer '26 ships API version 67.0. Salesforce's API end-of-life page lists 41.0 through 68.0 as supported, so v68.0 may appear in preview or in newer org documentation, and 67.0 is the safer production baseline as of 2 September 2026. That judgement is ours, not a Salesforce statement.

Two version details matter. GraphQL mutations were a beta service in API v59.0 through v65.0 and are generally available in v66.0 and later, so a mutation test on an older pin behaves differently. Pub/Sub is a gRPC API over HTTP/2, not a REST resource, so it needs a gRPC client rather than curl.

Set up a safe Salesforce test environment

Never point a test suite at production. Pick by data shape and refresh interval.

EnvironmentData shapeMinimum wait before another refresh
Developer sandboxMetadata copy, no production data, 200 MB storage1 day
Developer Pro sandboxMetadata copy, no production data, 1 GB storage1 day
Partial Copy sandboxTemplate-selected sample, up to 10,000 parent records and related records, or 5 GB5 days
Full sandboxFull production data copy, optional template reduction29 days

Scratch orgs work differently. They are source-driven and disposable, shaped by a configuration file, and you recreate them from source and seed data rather than refresh them. Salesforce gives them a 7-day default expiration and a 30-day maximum.

For most API tests, a Developer or Developer Pro sandbox, or a scratch org the test seeds itself, is the right target. Reach for Partial Copy or Full only when the assertion needs production-shaped data. A Full sandbox you pollute cannot be refreshed for 29 days, so cleanup belongs in the test. One more trap: sandboxes default to 5,000,000 API calls per 24 hours, high enough to hide a limit failure that would fire in production. The isolation rules are those for any API sandbox.

Authenticate with the OAuth 2.0 JWT bearer flow

Start with the app. Starting in Spring '26, creating new connected apps is disabled by default, and re-enabling it means asking Salesforce Support. Existing ones keep working, and Salesforce recommends External Client Apps for new integrations and for migrating old ones. So build against an External Client App. An existing connected app runs this flow unchanged.

The flow is unattended, which is what a CI job needs. Your test signs a JSON Web Token with an RSA private key. Salesforce verifies the signature against the public certificate you uploaded to the app and returns an access token, with no browser redirect and no user login. The consumer key goes in the iss claim, the integration username in sub, the Salesforce login URL in aud. The request is a POST to /services/oauth2/token with grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer. If the shape is new, read OAuth 2.0 first.

Get the host right. Use login.salesforce.com for production and Developer Edition, test.salesforce.com for a sandbox, or your My Domain login URL. Salesforce is explicit that this is the login URL, not the instance URL. Every data call afterwards goes to the instance_url the token response returns.

Keep the user small. Create a dedicated integration user and a permission set scoped to the objects and fields under test. A test that passes as System Administrator says nothing about the permissions the integration has.

One retirement fact, as Salesforce states it. SOAP login() is already unavailable in API v65.0 and later. In versions 31.0 through 64.0 it retires in Summer '27, and any integration authenticating with a username and password over SOAP will stop working. Salesforce points those at OAuth with external client apps and JWT. SOAP itself is fine: v67.0 accepts JWT access tokens in the sessionId header element.

Run your first Salesforce REST API test

This script needs curl, jq and OpenSSL, plus three variables: SF_CLIENT_ID (the app consumer key), SF_USERNAME (the integration username) and SF_PRIVATE_KEY (a path to the RSA private key). Set SF_LOGIN_HOST=https://test.salesforce.com for a sandbox; SF_API_VERSION defaults to 67.0. It prints neither the key nor the token.

#!/usr/bin/env bash
set -euo pipefail

: "${SF_CLIENT_ID:?Set the app consumer key}"
: "${SF_USERNAME:?Set the integration username}"
: "${SF_PRIVATE_KEY:?Set the path to the RSA private key}"

SF_LOGIN_HOST="${SF_LOGIN_HOST:-https://login.salesforce.com}"
SF_API_VERSION="${SF_API_VERSION:-67.0}"

b64url() {
  openssl base64 -A | tr '+/' '-_' | tr -d '='
}

header=$(printf '%s' '{"alg":"RS256","typ":"JWT"}' | b64url)
expires_at=$(( $(date +%s) + 180 ))
payload=$(
  jq -cn \
    --arg iss "$SF_CLIENT_ID" \
    --arg sub "$SF_USERNAME" \
    --arg aud "$SF_LOGIN_HOST" \
    --argjson exp "$expires_at" \
    '{iss:$iss,sub:$sub,aud:$aud,exp:$exp}' | b64url
)
unsigned="$header.$payload"
signature=$(printf '%s' "$unsigned" | openssl dgst -sha256 -sign "$SF_PRIVATE_KEY" | b64url)
assertion="$unsigned.$signature"

token_json=$(
  curl -sS "$SF_LOGIN_HOST/services/oauth2/token" \
    --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' \
    --data-urlencode "assertion=$assertion"
)
access_token=$(jq -er '.access_token' <<<"$token_json")
instance_url=$(jq -er '.instance_url' <<<"$token_json")

query_json=$(
  curl -sS --get \
    -H "Authorization: Bearer $access_token" \
    "$instance_url/services/data/v$SF_API_VERSION/query" \
    --data-urlencode 'q=SELECT Id,Name FROM Account LIMIT 1'
)

jq -e '
  .done == true and
  (.totalSize | type == "number") and
  (.records | type == "array")
' <<<"$query_json" >/dev/null

printf 'Salesforce REST assertion passed\n'

Four stages. It builds the JWT header and claims and signs them with RS256, base64url-encoding each part. It exchanges the assertion for an access token. It queries one Account through the returned instance_url. Then it asserts.

Read the assertions one at a time. .done == true says the query completed rather than returning a first page with more to fetch. .totalSize must be a number and .records an array, which together prove you got a query result and not an error object wearing a 200. Salesforce's own REST examples return these three fields. set -euo pipefail plus jq -e exit non-zero the moment anything fails, the only behaviour CI can act on.

To point it at a custom object, change the SOQL string and nothing else. And call the instance_url from the token response, not the login host. The login host authenticates you and the instance host serves your data, so hardcoding the first is why a working script breaks after an org move. REST also applies the calling user's object and field permissions, so the same query returns different fields for different users. For the method behind these checks, see the REST API test design guide.

Test SOAP, Bulk 2.0, Pub/Sub and GraphQL behavior

Each remaining family fails in its own way. The shape of the test for each:

SOAP. Use the v67.0 enterprise or partner WSDL and put the access token in the sessionId header element. Assert the typed response matches the WSDL contract, field for field. Then send a deliberately bad request and assert it comes back as a SOAP fault, not a 200 carrying an error string. That second test catches middleware that swallows faults.

Bulk API 2.0. Create a job, poll it until it finishes, then read the failed rows and assert on their count and their messages. Asserting on the job state alone is the trap: a job can finish while every row inside it failed.

Pub/Sub. Call the eventbus.v1 service. Subscribe with a replay ID, publish a test event, assert the Avro payload matches the event schema. Then replay from the stored ID and assert there are no duplicates and no gaps. Duplicate delivery is the failure that survives every other test.

Streaming. The legacy path. Keep maintenance tests for existing subscriptions and write new event tests against Pub/Sub.

GraphQL. POST to /services/data/v67.0/graphql on the instance_url the token response returned. For the positive case, assert data is present and errors absent. Then run the same query as a lower-privilege user and assert on the fields the schema hides. See GraphQL API testing for the query-level assertions.

Test permissions and API security

Two negative tests carry most of the value. Request a field the integration user cannot read, and attempt a write it cannot perform. Expect the field absent or the request errored, and the write rejected. A 200 that leaks a forbidden field is a failure, even though nothing in the status code says so. Salesforce's troubleshooting guidance shows the read failure: a field without field-level security returns No such column on the entity, not an empty value.

Add two more. An expired or revoked token returns INVALID_SESSION_ID, and a token minted for one org must fail when sent to another.

Run all of it with the real integration profile. If a test needs more access, change the permission set deliberately and write down why, rather than widening the user until the suite goes green. Keep the private key in the CI secret store, never the repository, the first item in key and token handling. The wider sweep is the API security checklist.

Test API limits and failure handling

There is no single Salesforce daily API limit. It is per edition and per license, and it applies to the whole org, not per user.

Salesforce editionAPI calls per license type per 24 hoursTotal calls per 24 hours
Developer EditionN/A15,000
Enterprise, and Professional with API access enabledSalesforce 1,000, Salesforce Platform 1,000, Lightning Platform One App 200100,000 + (licenses x calls per license type) + purchased API Call Add-Ons
Unlimited and PerformanceSalesforce 5,000, Salesforce Platform 5,000, Lightning Platform One App 200The same formula
Sandbox defaultN/A5,000,000

Those figures come from Salesforce's Developer Limits and Allocations Quick Reference, read on 2 September 2026. REST, SOAP, Bulk API and Bulk API 2.0 all draw from the same pool, so a chatty Bulk job starves the REST tests beside it. Read consumption from the Sforce-Limit-Info header, or the System Overview and Company Information pages in Setup.

ErrorWhat it meansTest and fix
INVALID_SESSION_IDToken expired, revoked, wrong user context, or wrong hostGet a new token, call the returned instance_url. Assert the client re-authenticates once, then fails clearly
REQUEST_LIMIT_EXCEEDEDThe shared 24-hour pool is emptyAssert a controlled backoff, read Sforce-Limit-Info, stop non-essential polling. Never treat a sandbox's 5,000,000 as a forecast
Missing field, No such column, rejected writeField-level security, an object permission or sharing blocks itRun with the real integration profile. Assert allowed and forbidden fields. Widen the permission set only when access is required
HTTP 410 GONEThe request uses a retired API versionPin the version in configuration, test against release preview, upgrade before the retirement date

Use the official Postman collection, not Workbench, by default

Salesforce publishes a Postman collection in its Salesforce Developers workspace, with a configurable environment and auth variables that point the same requests at several orgs. Salesforce's own Connect Postman to Salesforce page is direct about the succession: "Postman replaces the legacy Workbench tool as the recommended solution to explore and test Salesforce APIs." Workbench still works if you need it.

Explore in Postman, and keep the test above tool-independent so CI can run it. That collection also feeds the next section.

Turn the test into a pull-request gate

A manual call proves the API works once. A gate proves it still works on the change in front of you. On every pull request, create a scratch org from source and seed data, or reuse a long-lived sandbox and clean up after the run. Refreshing a sandbox per pull request is not an option: the minimum wait is one day for a Developer sandbox and 29 days for a Full one. Run the REST test above, the two permission negatives, a Bulk job poll, a Pub/Sub replay check and a limit budget check. Store the request, the redacted response, the API version, the org ID and each assertion result, so a failure arrives with proof. Replay the same saved inputs after every change, and fail the merge on any failed assertion, leaked field or crossed budget. The general shape is continuous API testing.

Qodex does the second half. Point it at an OpenAPI spec or the Postman collection above and it generates runnable scenarios with auth and role boundaries included. Those run against the pull request's own preview, on demand, on a schedule, from CI, a deploy hook or any webhook. One scenario is parameterized per environment with the values swapped in, which is how the sandbox host and integration user arrive. Every failure returns the request, the response and a screenshot, classified as a real bug, a stale test with a repair diff you approve, or an environment issue flagged and not counted. A replay costs $0 in model spend, and the tests are standard Playwright and HTTP code you own. Creating, refreshing and seeding the org stays in your pipeline. Run Salesforce API scenarios on every pull request with Qodex.

Salesforce API testing checklist

Ten things to have in place before the suite is done, all following the general API testing method.

  • A pinned API version, 67.0 today.

  • An isolated org with a known refresh or recreate rule.

  • A least-privilege integration user, never System Administrator.

  • Seeded data the test owns and deletes.

  • Positive assertions on status, body and records.

  • Negative assertions on hidden fields and forbidden writes.

  • A limit budget read from Sforce-Limit-Info.

  • Retry rules for INVALID_SESSION_ID and REQUEST_LIMIT_EXCEEDED.

  • Replay or idempotency checks for Bulk and Pub/Sub.

  • Cleanup, and stored evidence per run.

Frequently Asked Questions

How do I test the Salesforce API?

Get an access token with the OAuth 2.0 JWT bearer flow, then send one SOQL query to the instance_url the token response returns. Assert the response shape with something that exits non-zero, such as jq -e, so CI can fail on it.

Is there an API for Salesforce?

Yes, several. REST handles synchronous CRUD and SOQL. SOAP serves WSDL consumers. Bulk API 2.0 runs asynchronous jobs. Pub/Sub carries events over gRPC. Streaming is the legacy event path. GraphQL fetches related records in one request.

What exactly is API testing?

Sending real requests to an API and checking the status, the body, the side effects and the errors, with no browser in the way. Two things differ on Salesforce: the calling user's permissions shape the response, and every call draws from a shared allocation.

What are the different types of testing in Salesforce?

Apex unit tests run inside the org against Apex code. API tests call the platform APIs from outside. UI tests drive Lightning pages in a browser. Integration and end-to-end tests span systems. API tests sit between the other two.

Which Salesforce API should I use: REST, SOAP, Bulk, Pub/Sub or GraphQL?

REST by default. Bulk API 2.0 once a job passes roughly 2,000 records. SOAP when the consumer needs a WSDL contract. Pub/Sub for events. GraphQL when one request must return related records with only the fields you name.

Can I test Salesforce APIs in a sandbox or scratch org?

Yes. Authenticate against test.salesforce.com for a sandbox. A Full sandbox not built from a template allows 5,000,000 calls per 24 hours, so limits will not behave like production. Scratch orgs expire in 7 days by default, 30 at most.

What is the Salesforce daily API request limit?

There is no single number. It is per edition and per license, over a rolling 24 hours. Developer Edition gets 15,000. Enterprise Edition gets 100,000 plus per-license calls plus add-ons. Read actual consumption from the Sforce-Limit-Info header.

What causes INVALID_SESSION_ID in Salesforce API tests?

An expired or revoked access token, a token sent to the wrong host, or a token from a different org. Request a new token and call the instance_url from the token response. Retry authentication once, then fail loudly.

The general method, the tool choices and the rest of the guides are in the API testing hub.

Ship continuously. Test continuously.

Qodex explores your app, writes runnable tests, and replays them on every change at zero LLM cost.