Not verified hands-on. This article was written from primary documentation rather than reproduced in an org, so it carries no tested-release badge. If you run it and something differs, say so in the comments and it gets tested and corrected.

The short answer: an API version is a frozen contract. The version number in your request URL, your WSDL or your Apex class metadata decides which fields exist, which behaviours apply and which bugs you still get. Salesforce retires old versions on a published schedule, and when a version you depend on goes, external calls against it start failing outright. Auditing which versions your org actually uses takes an afternoon and prevents a very bad week.

What pinning actually means

Every REST call names its version in the path: /services/data/v63.0/sobjects/Account. That number is not decoration. It fixes the shape of the response, the set of fields the describe returns, the default values applied on insert, and the error codes you get back. Two versions of the same endpoint can return different JSON for the same record.

SOAP is stricter still. The WSDL you downloaded is bound to the version current when you downloaded it, and the generated client classes encode that structure. A field added in a later release is simply not in your object graph until you regenerate.

Apex is the case people forget. Each class, trigger, page and component carries its own API version in metadata, set when it was created and never changed unless somebody changes it. A class written against an old version keeps the old runtime behaviour for things Salesforce has since altered, which is the point: your code does not silently change meaning when a release ships.

SELECT Name, ApiVersion, LastModifiedDate
FROM ApexClass
WHERE ApiVersion < 50.0
ORDER BY ApiVersion ASC

What breaks when a version retires

Salesforce retires bands of old versions periodically and announces them well in advance, usually with a long deprecation window and a period where calls still work but a warning is logged. Once retirement lands, an external request naming a retired version gets an error response rather than data. That is the outage: your middleware, your mobile client, your nightly extract, all failing at once with something unhelpful about an unsupported version.

Apex behaves differently. Classes saved against a retired version continue to run. Salesforce has been consistent about not breaking existing Apex, which is why orgs are full of classes on versions nobody has thought about in a decade. The risk there is not failure, it is drift: you cannot call a newer method from an old-version class, and test behaviour diverges in ways that waste hours.

Finding what you actually use

Three places to look, in order of usefulness.

  • Event Monitoring. The API Total Usage event type records the client name and API version for inbound calls. If you have Shield or the event log add-on, this is the definitive answer and the only one that sees traffic you forgot about.
  • Setup notifications. Salesforce emails org admins when traffic arrives on a version scheduled for retirement. Those emails go to a mailbox nobody reads. Find out which one.
  • Your own metadata. Query ApexClass, ApexTrigger, ApexPage, AuraDefinitionBundle and LightningComponentBundle for their API versions. That covers what you built. It says nothing about what calls you.

The gap is always the same: a script on a server nobody owns, running as a user nobody remembers, on a version from 2016. Event Monitoring finds it. Nothing else reliably does.

A policy worth having

Pin deliberately, one version for the whole integration estate, and move it on a schedule rather than when forced. Sitting two or three releases behind current is a reasonable place to be: you get stability without being anywhere near the retirement band.

Do not let each client pick its own. Fifteen integrations on eleven versions is a migration you will never finish. Put the version in one configuration value per client and test the bump in a sandbox first, watching for changed defaults rather than outright errors.

For Apex, bump class versions when you touch the class and have tests covering it. A bulk update of every class to the newest version is a tempting one-liner and a genuinely bad idea.

Frequently asked

Can I just always call the latest version? You can, and some tools do it by fetching /services/data/ and taking the highest entry. It means your integration’s behaviour changes three times a year without a release of your own. Most teams regret it.

What version should a brand new integration use? The current one at build time, recorded in a config value with a comment saying when it was chosen. Then revisit annually.

Will my old Apex stop working? No. Existing Apex on retired versions keeps running. Only external API calls naming a retired version fail.