Use when designing, implementing, or reviewing Salesforce SOAP API integrations — covering enterprise vs partner WSDL selection, login() and session management, core operations (query/create/update/upsert/delete), and Metadata API SOAP usage. Trigger keywords: 'enterprise WSDL', 'partner WSDL', 'login() call', 'sessionId', 'SOAP API', 'WSC', 'force-wsc', 'Ant Migration Tool', '.NET SOAP integration'. NOT for REST API, Bulk API 2.0, or GraphQL API.
Use when designing, implementing, or reviewing Salesforce SOAP API integrations — covering enterprise vs partner WSDL selection, login() and session management, core operations (query/create/update/upsert/delete), and Metadata API SOAP usage. Trigger keywords: 'enterprise WSDL', 'partner WSDL', 'login() call', 'sessionId', 'SOAP API', 'WSC', 'force-wsc', 'Ant Migration Tool', '.NET SOAP integration'. NOT for REST API, Bulk API 2.0, or GraphQL API.
["should I use the enterprise WSDL or the partner WSDL for my integration","how do I authenticate to the Salesforce SOAP API and manage the session","my .NET or Java integration uses WSDL-generated stubs and I need to query or update records","when should I use SOAP API instead of REST API for a Salesforce integration","the Metadata API requires SOAP — how do I set up login and deploy metadata","build a relationship SOQL query against the partner WSDL","run describeSObjects to find the relationshipName for a partner-WSDL subquery"]
inputs
["integration consumer type: internal org integration vs ISV/cross-org integration","development platform: .NET (Visual Studio), Java (WSC), or other SOAP toolkit","authentication context: username-password login() vs OAuth session ID injection","target org edition and API version to confirm WSDL download location and endpoint","operation type: DML (create/update/upsert/delete), query/queryMore, or Metadata API deploy/retrieve"]
outputs
["WSDL selection decision with rationale (enterprise vs partner)","login() flow with sessionId extraction and serverUrl configuration","endpoint URL pattern for the selected API version","operation call structure for query(), queryMore(), create(), upsert(), and delete()","session expiry handling strategy","review findings for an existing SOAP integration"]
dependencies
[]
version
1.1.0
author
Pranav Nagrecha
updated
"2026-07-08T00:00:00.000Z"
SOAP API Patterns
Use this skill when the integration task involves the Salesforce SOAP API — whether you are selecting between the enterprise and partner WSDLs, implementing a login-and-session lifecycle, calling core DML or query operations, or working with the Metadata API (which is exclusively SOAP-based). This skill also covers the decision of when SOAP is the right choice over REST.
Before Starting
Gather this context before working on anything in this domain:
WSDL type needed: Is this integration internal to a single org (enterprise WSDL) or a product/ISV that must work with any org (partner WSDL)?
Development platform: What SOAP toolkit generates the client stubs? Java uses WSC (force-wsc), .NET uses Visual Studio WSDL import, other languages use their native SOAP client.
API version: Which version is the integration targeting? The endpoint URL must include the version explicitly (e.g., https://<instance>.salesforce.com/services/Soap/c/63.0 for enterprise, https://<instance>.salesforce.com/services/Soap/u/63.0 for partner). Spring '25 = v63.0.
Authentication method: Is this a direct login() call with username/password/security-token, or is an OAuth session ID being injected into SOAP headers (preferred for connected apps)?
Org edition and daily API limits: Enterprise Edition allows up to 1,000 API calls per Salesforce license per 24-hour period (minimum 1,000 total); Developer Edition is limited to 15,000 per day. SOAP and REST calls share the same daily limit pool.
Core Concepts
Enterprise WSDL vs Partner WSDL
The Salesforce SOAP API ships two distinct WSDLs with different type contracts:
Enterprise WSDL
Partner WSDL
Type contract
Strongly typed — each object and field has a generated Java/C# class
Weakly typed — generic sObject with string-keyed field maps
Org-specificity
Org-specific — generated from your org's schema, includes custom objects and fields
Generic — works with any Salesforce org without regeneration
When to use
Internal integrations for a specific org where developer productivity and compile-time safety matter
ISVs, packages, and cross-org integrations that must work against multiple or unknown orgs
WSDL endpoint
Setup > API > Generate Enterprise WSDL
Setup > API > Generate Partner WSDL
SOAP service URL
/services/Soap/c/<version>/
/services/Soap/u/<version>/
Critical drawback
Must be re-downloaded and regenerated every time custom fields or objects are added or changed
Both WSDLs expose the same operations (login, query, create, update, upsert, delete, etc.). The difference is entirely in the type surface exposed to the client.
The Metadata API also requires WSDL-based access. Authentication is established via the enterprise or partner WSDL login() call first, and the resulting sessionId is then used to initialize the Metadata API connection.
Authentication: login() and Session Management
The canonical SOAP authentication flow calls login() on the SOAP endpoint and extracts two values from the LoginResult:
sessionId — the session token included in every subsequent SOAP call via the CallOptions or SessionHeader SOAP header.
serverUrl — the instance-specific URL to use for all subsequent API calls. The login endpoint is a generic URL; after login you must switch to the serverUrl. Failing to do so causes calls to fail on sandbox and non-na1 instances.
serverUrl — use this as the base for all subsequent SOAP calls
sessionId — include in SessionHeader on every subsequent call
userId, userInfo — metadata about the authenticated user
Session expiry: Sessions expire after the org's configured timeout (default 2 hours for most editions, configurable from 15 minutes to 24 hours in Setup > Session Settings). Expired sessions return INVALID_SESSION_ID. Integrations must catch this fault and re-authenticate — either by calling login() again or by refreshing the OAuth access token if using OAuth session injection.
Security token: When calling login() with username/password from an IP not in the org's trusted range, the security token must be appended directly to the password string (no separator). Use OAuth flows to avoid embedding credentials.
Core SOAP Operations
Operation
Description
Notes
query()
Execute a SOQL query; returns up to queryBatchSize records
Returns QueryResult with done flag and queryLocator
queryMore()
Fetch the next page of a query() result
Required when done is false; use the queryLocator from prior result
create()
Insert one or more sObjects (up to 200 per call)
Returns SaveResult[] with per-record success/error
update()
Update one or more sObjects by Salesforce ID (up to 200 per call)
Returns SaveResult[]
upsert()
Create-or-update using an external ID field (up to 200 per call)
Requires an indexed External ID field; returns UpsertResult[]
delete()
Delete one or more records by ID (up to 200 per call)
Soft delete; records go to Recycle Bin
merge()
Merge up to 3 records into a master record
Returns MergeResult; merged records are deleted
retrieve()
Fetch specific fields for a list of record IDs
Efficient alternative to query when IDs are known
getUpdated() / getDeleted()
Retrieve IDs of records changed or deleted in a time window
Useful for CDC-style polling when CDC/Streaming is not available
Each DML call accepts an array of up to 200 records. Salesforce processes all records in the call even if some fail — there is no all-or-none semantics by default (unlike REST Composite with allOrNone: true). Inspect each SaveResult or UpsertResult for per-record success and errors.
Relationship Queries with the Partner WSDL
Relationship SOQL — a parent-to-child subquery like SELECT Id, (SELECT Id FROM Assets) FROM Account, or a child-to-parent dot-walk like SELECT Id, Owner.Name FROM Case — behaves differently under the partner WSDL because of its loosely-typed design. The partner WSDL "defines a single, generic object (sObject) that represents all the objects," so it "doesn't contain the detailed type information that's available in the enterprise WSDL which you need for a relationship SOQL query." The relationship names and reference-field names an enterprise-WSDL client reads from generated classes simply are not in the partner stubs.
The partner-WSDL workflow is therefore describe-first:
Call describeSObjects() for the object you intend to query. The returned DescribeSObjectResult carries the relationship metadata the WSDL omits.
Parent-to-child (one-to-many) subqueries — read the relationshipName from the child relationships and use it as the subquery source. Example: Assets is the relationshipName for the child assets of an Account, giving SELECT Id, (SELECT Id, Name FROM Assets) FROM Account.
Child-to-parent traversal — identify the reference (lookup/polymorphic) fields, such as WhoId, WhatId, or OwnerId on a Lead, Case, or custom object, and dot-walk through them: SELECT Id, Owner.Name FROM Case.
Two follow-on traps come from the generic representation:
Nested records are still generic sObjects. A relationship query returns child records as the same untyped sObject, never a generated class. Resolve each nested record's real type from describe metadata — "for a particular object, its type is defined in the name field in the returned DescribeSObjectResult" — rather than casting to a compile-time type.
Field order follows the query, not the WSDL. "The ordering of fields in the QueryResult is determined by the field order in the fieldList, not the field order in the WSDL file." Parse partner-WSDL results positionally against the SELECT list you sent, which matters most when walking nested relationship results generically.
Enterprise-WSDL clients avoid all of this: relationship names and typed accessors are baked into the generated classes, so relationship queries compile against known types with no describe step.
When to Use SOAP Over REST
Prefer REST API for new integrations — it is simpler, more widely supported, and the Salesforce product direction favors it. Use SOAP API when:
Metadata API is involved — The Metadata API is exclusively SOAP. Deployments via the Ant Migration Tool, Java/WSC-based deploy tools, and any retrieve/deploy operation must use SOAP.
Legacy .NET or Java codebase already using WSDL-generated stubs — Migrating a working integration to REST has cost and risk. Maintaining SOAP is reasonable if the integration is stable.
Enterprise-grade SOAP toolkit ecosystem — WS-Security, SOAP intermediaries, and certain ESB/middleware products (MuleSoft, IBM MQ, SAP PI) have first-class SOAP connectors. Using REST in these environments may require lower-level HTTP configuration.
Compound field access on Address/Geolocation — SOAP returns Address and Geolocation as nested complex types in the response, which some ETL tools handle better than the REST API's flat-field representation.
Common Patterns
Mode 1 — Implement a New SOAP Integration
Step 1 — Choose WSDL type. Internal to a single org? Use enterprise WSDL for compile-time safety. ISV or multi-org product? Use partner WSDL.
Step 2 — Download and generate stubs. In Setup > API, download the WSDL file. Use the platform's SOAP toolkit to generate client classes. For Java with WSC:
Step 3 — Implement login() and capture serverUrl + sessionId.
ConnectorConfigconfig=newConnectorConfig();
config.setAuthEndpoint("https://login.salesforce.com/services/Soap/c/63.0");
config.setUsername("user@example.com");
config.setPassword("passwordSECURITYTOKEN");
EnterpriseConnectionconnection= Connector.newConnection(config);
// config.getServiceEndpoint() now contains the instance serverUrl// connection carries the sessionId automatically via WSC
Step 4 — Execute operations and inspect per-record results.
SObjectaccount=newSObject();
account.setType("Account");
account.setField("Name", "Acme Corp");
SaveResult[] results = connection.create(newSObject[]{ account });
for (SaveResult result : results) {
if (!result.isSuccess()) {
for (Error error : result.getErrors()) {
System.err.println(error.getStatusCode() + ": " + error.getMessage());
}
}
}
Step 5 — Implement session expiry handling. Catch InvalidSObjectFault / INVALID_SESSION_ID faults, call login() again, and retry.
Mode 2 — Review an Existing SOAP Integration
Check for these issues in priority order:
Is serverUrl used after login? Hardcoded production endpoints break sandbox or EU instances. The serverUrl from LoginResult must be the base for all subsequent calls.
Is the API version current? Endpoint URLs containing /Soap/c/20.0 through /Soap/c/40.0 are in the deprecated range. Salesforce retires API versions; update to v56.0 or later.
Are SaveResult[] / UpsertResult[] errors inspected per record? SOAP DML calls partially succeed — checking only the HTTP response code or outer fault misses record-level failures.
Is the enterprise WSDL up to date? If custom fields or objects were added since the last WSDL regeneration, those fields are inaccessible from the stubs.
Are credentials hardcoded?login() password with embedded security token must not be stored in source code.
Is queryMore() called when done is false? Failing to paginate silently truncates query results.
For partner-WSDL relationship queries: are relationshipName and reference-field names discovered via describeSObjects()? Partner-WSDL clients have no compile-time relationship metadata, and nested results must be type-resolved from describe metadata rather than cast to a generated class.
Mode 3 — Troubleshoot a SOAP Integration
Symptom
Likely Cause
Fix
INVALID_SESSION_ID fault
Session expired or wrong session header
Re-authenticate with login() and update SessionHeader
UNSUPPORTED_API_VERSION (500)
API version in endpoint URL is retired
Update endpoint URL to a current version (v56.0+)
Records found in login org, not in data org
serverUrl ignored; still using login endpoint
Switch all post-login calls to serverUrl from LoginResult
Custom fields return null or don't exist in stubs
Enterprise WSDL was not regenerated after field creation
Re-download enterprise WSDL and regenerate stubs
REQUIRED_FIELD_MISSING on create
Org validation rule or required field not set in request
Inspect SaveResult.errors for specific field names
queryMore() returns fault
queryLocator from prior page is stale (session reset)
Handle session expiry and re-execute query from scratch
Session expires mid-batch
Default session timeout reached during long operations
Increase session timeout in Setup > Session Settings or implement proactive re-authentication
Decision Guidance
Situation
Recommended Approach
Reason
New integration from scratch
REST API
Simpler, JSON-native, broader modern tooling support
Salesforce requires token when IP not in trusted range
Auth in production systems
OAuth JWT Bearer or Web Server flow
Avoids embedding credentials; tokens are scoped and revocable
Query result > configured queryBatchSize
query() + queryMore() loop
query() returns partial results; must call queryMore() until done = true
Recommended Workflow
Step-by-step instructions for an AI agent or practitioner activating this skill:
Gather context — confirm the org edition, relevant objects, and current configuration state
Review official sources — check the references in this skill's well-architected.md before making changes
Implement or advise — apply the patterns from Core Concepts and Common Patterns sections above
Validate — run the skill's checker script and verify against the Review Checklist below
Document — record any deviations from standard patterns and update the template if needed
Review Checklist
Run through these before marking work in this area complete:
WSDL type (enterprise vs partner) is appropriate for the consumer type (internal vs ISV/multi-org).
Post-login calls use serverUrl from LoginResult, not a hardcoded endpoint.
API version in endpoint URL is at or above v56.0 (ideally v63.0 for Spring '25).
For enterprise WSDL integrations: WSDL was regenerated after any org schema changes.
Session expiry (INVALID_SESSION_ID) is handled with re-authentication, not just logged as fatal.
All SaveResult[] / UpsertResult[] arrays are inspected per record — partial DML success is expected.
queryMore() is called when QueryResult.isDone() is false; results are not silently truncated.
Credentials and security tokens are not stored in source code or configuration files in version control.
Daily API call volume estimate fits within the org's edition limit (SOAP and REST share the same pool).
Compound fields (Address, Geolocation) are handled as nested complex types, not flat strings.
For partner-WSDL relationship queries: describeSObjects() is called first to obtain relationshipName / reference-field names, and nested records are type-resolved from describe metadata rather than cast to a generated class.
Salesforce-Specific Gotchas
Non-obvious platform behaviors that cause real production problems:
Enterprise WSDL becomes stale when org schema changes — The enterprise WSDL is generated from your org's exact schema at a point in time. Adding or changing a custom field after generation makes that field invisible to the generated stubs. The integration will not error — it will silently omit the field. Regenerate the WSDL and rebuild stubs any time the org schema changes.
serverUrl must replace the login endpoint for all subsequent calls — The login() call targets login.salesforce.com (or test.salesforce.com for sandbox). The LoginResult.serverUrl is the instance-specific URL you must use for all subsequent SOAP calls. Ignoring serverUrl and continuing to use the login host causes requests to fail on any instance other than na1, including all EU, APAC, and Hyperforce instances.
Session expiry kills in-progress batch operations — If a query() + queryMore() loop runs longer than the org's session timeout, the queryLocator becomes invalid and queryMore() returns INVALID_SESSION_ID. The entire query must restart from scratch after re-authentication. Mitigation: set a longer session timeout in Setup > Session Settings, or break large queries into smaller date-range slices that complete within a single session window.
SOAP DML calls partially succeed — there is no implicit rollback — Unlike REST Composite with allOrNone: true, a SOAP create() or update() call with 200 records will process all records regardless of individual failures. The response array contains a SaveResult per record; some will have success=true and others success=false. Integrations that only check the HTTP 200 outer response will miss failed records.
Security token appended, not separate — The login() password parameter is the user's password with the security token appended as a single string (e.g., mypasswordXYZTOKEN). There is no separator. A common mistake is passing the token as a separate parameter, which results in a LOGIN_MUST_USE_SECURITY_TOKEN fault.
Output Artifacts
Artifact
Description
WSDL selection rationale
Enterprise vs partner decision with justification for the integration context
Login flow implementation
login() call pattern, serverUrl capture, and SessionHeader wiring
Operation call structure
Create/update/upsert/delete/query call scaffolds with per-record result inspection
Session expiry handler
Fault detection and re-authentication pattern
Review findings
Itemized issues from the Mode 2 review checklist with severity and remediation
Related Skills
integration/rest-api-patterns — use for all new integrations where SOAP is not required; REST is the preferred API for new development.
integration/oauth-flows-and-connected-apps — use when replacing login() username/password with OAuth JWT Bearer or Web Server flows.
integration/graphql-api-patterns — use when the consumer needs flexible, field-selective queries from a single endpoint.
data/bulk-api-and-large-data-loads — use when record volumes exceed what SOAP DML (200 records/call) can handle efficiently.