Founding keys are opena year free at 6,000 req/hour, for the first 1,000 developersClaim yours
Documentation 29

Salesforce API quickstart · 4 min read

A Salesforce instance URL you can point at, with a real CRM behind it.

Four operations: the API version list captured verbatim from a real org, plus sObject Describe, retrieve-by-Id and a bounded SOQL query over Accounts, Contacts, Opportunities and Cases. Everything else answers Salesforce's own 404 with a coverage header. This page says exactly what is here, what is not, and how strong the evidence behind each is.

01Point your client at sf.
02Read what is served today
03Pin a snapshot for CI

01 / Read this first

Four operations, over four objects.

This host serves the core of what an integration needs to read a CRM, and nothing beyond it:

served.txt
GET /services/data                                    the API version list
GET /services/data/v67.0/sobjects/{obj}/describe      the object's metadata
GET /services/data/v67.0/sobjects/{obj}/{id}          one record
GET /services/data/v67.0/query?q=<SOQL>               a bounded SOQL query

{obj} = Account | Contact | Opportunity | Case

Those four objects are one company's real go-to-market: eighteen accounts, sixty-eight contacts, twenty-eight opportunities moving through Salesforce's own nine-stage pipeline, and one hundred and forty-six cases — including the ones customers filed during the outage the rest of this universe already tells you about. A Case here names the same incident PagerDuty paged on and the same engineer the git host lists as an org member.

Everything else — describeGlobal, /queryAll, list views, search, limits, analytics, bulk — answers Salesforce's own 404 NOT_FOUND envelope with an x-sandboxapis-coverage header.

See the full row-by-row coverage → — every one of the 50 read operations is enumerated and classified, with a written reason for the 29 that are not served yet.

02 / SOQL

A bounded subset that refuses by name.

The query endpoint parses a deliberate subset of SOQL — the part real integrations actually send:

soql.txt
SELECT <fields> | COUNT()
  FROM Account | Contact | Opportunity | Case
[ WHERE <conditions> ]        AND · OR · NOT · parentheses
                              =  !=  <  <=  >  >=  ·  IN · NOT IN · LIKE
[ ORDER BY <field> [ASC|DESC] [NULLS FIRST|LAST] ]
[ LIMIT n ] [ OFFSET n ]

values:  'text' · 123 · TRUE · FALSE · NULL
         2026-04-27 · 2026-04-27T14:22:36Z
         TODAY · YESTERDAY · TOMORROW · LAST_N_DAYS:n · NEXT_N_DAYS:n
         THIS_MONTH · LAST_MONTH · THIS_YEAR · LAST_YEAR

Relative dates resolve against the universe's today, not the wall clock — so WHERE CreatedDate = LAST_N_DAYS:7 returns the same rows against a pinned snapshot in two years as it does now.

03 / Base URL + auth

It is an instance URL, not a hostname.

Salesforce clients take a whole instance URL rather than composing one, so the swap is a single value: https://sf.sandboxapis.dev. Paths are Salesforce's own and start at /services/data. Auth is Authorization: Bearer …; any token passes here, and so does none — there is no org to create and no connected app to register.

request.sh
curl "https://sf.sandboxapis.dev/services/data"

Using a key? Keys & rate limits → — a SandboxAPIs key rides the same Bearer slot and lifts the anonymous limit.

04 / What is served

Thirty-seven versions, and one curly apostrophe.

GET /services/data returns the exact array a real Salesforce instance returned on 4 September 2026 — same order, same key order, same content type (application/json;charset=UTF-8). Both spellings of the path work, with and without the trailing slash, because both do upstream.

versions.json
[
  { "label": "Summer '14", "url": "/services/data/v31.0", "version": "31.0" },
  …
  { "label": "Spring ’18", "url": "/services/data/v42.0", "version": "42.0" },
  …
  { "label": "Summer '26", "url": "/services/data/v67.0", "version": "67.0" }
]

Look closely at v42.0. Thirty-six of those labels use an ASCII apostrophe; that one uses a curly right single quotation mark. That is Salesforce's inconsistency, sitting in their production response, and we mirror it rather than tidy it — normalising a capture would turn it into something we made up, and a client byte-comparing against the real API would then see a difference we introduced.

05 / Provenance

A weaker guarantee, stated plainly.

Most hosts here validate against a machine-readable description the provider publishes. Salesforce publishes none for its core REST API, and developer.salesforce.com answers HTTP 403 to every machine fetch — the documentation pages, the document index and the JSON content endpoint alike — so there is no page we can pin and re-diff either.

So the shape of record here is hand-authored, and it says per operation what it claims. Three different things, and the difference is not decoration:

evidence.txt
capture        1 op    GET /services/data
                      our response IS the response Salesforce gave, byte for byte.

hand-authored  3 ops   describe · retrieve-by-Id · query
                      our response matches a schema WE wrote from Salesforce's
                      documented types. Nobody has diffed it against a live org.

path-only     56 ops   the path, method and parameters are enumerated.
                      No response shape is claimed at all.

The middle row is the one to read carefully, because it is what the CRM runs on. We cannot check those three shapes against a real org from outside one: Salesforce runs authentication before routing, so an anonymous caller only ever sees INVALID_SESSION_ID under a version segment. The error codes the query endpoint returns — MALFORMED_QUERY, INVALID_TYPE, INVALID_FIELD — are documented Salesforce codes, chosen because they are what the real API returns for exactly these situations, but they too are unobserved.

What that does guarantee is worth stating too, because "hand-authored" is not "unchecked". The describe payload, every record and every SOQL result are generated from one field table, and our conformance suite binds that table to the compiled universe. So a field that shows up in a describe but not in a record, a query that returns rows the artifact does not hold, or an Id that stops matching its record all fail our build. It is drift on Salesforce's side that we would not catch.

06 / Read-only + coverage

Every refusal is Salesforce-shaped.

This universe never changes. A write returns Salesforce's own error envelope — an array of {message, errorCode}, never a bare object — at 405 with Allow: HEAD,GET, opening with upstream's exact sentence and then naming what you tried and where the roadmap is. An unknown API version returns Salesforce's real 404 NOT_FOUND with no coverage header, because that one is genuinely upstream's answer. A path we do not cover returns the same body with the header.

refusals.json
# a write
[{"errorCode":"METHOD_NOT_ALLOWED","message":"HTTP Method 'POST' not allowed. Allowed are HEAD,GET. …"}]

# an unknown version — upstream's own 404, no coverage header
[{"errorCode":"NOT_FOUND","message":"The requested resource does not exist"}]

One thing we deliberately do not mirror: real Salesforce answers 401 to an anonymous caller on every versioned path, because its auth layer runs before routing. We never do — hiding the coverage boundary behind a credential wall would make "not covered" and "not authenticated" look identical.

07 / Pin for CI

A frozen universe, by hostname.

Point CI at https://sf-v67-g11.snap.sandboxapis.dev and the bytes stop moving. This pin was frozen on generation hello-14, the generation the CRM ships with, so the accounts, contacts, opportunities and cases on this page resolve there exactly as they do on the live host.

ci.sh
export SF_INSTANCE_URL="https://sf-v67-g11.snap.sandboxapis.dev"