콘텐츠로 이동

The Contract

이 콘텐츠는 아직 번역되지 않았습니다.

Module 2 of 6 in the chapter APIs, Contracts and Data Across the Wire, chapter 3 of the Integration Architect course. Each module stands on its own. Previous: The REST Request, Taken Apart · Next: The Star Trek Transporter

The question this module answers: What does a server promise its clients, how is that promise written down, and what happens when it changes?

In the last module, you took a call apart: the verb, the address, the headers, the body and the status. But how did you know which verb to use, which address, what to put in the body? You didn’t guess. Somebody told you. That’s the contract.

In the client–server tango, the server defines the contract. The client follows it. The server decides the protocol, the contract, and what the payload will be.

A contract answers the questions every client has. What are the verbs? Get orders. What are the nouns? Orders, customers. What’s the payload: what do I send, and what comes back? And it answers them for the bad days too. As we saw in the tango, the server has to fulfill the contract for both successful and unsuccessful things. So a good contract lists the errors it can send back, and what each one looks like, as carefully as it lists the answers.

Here’s something that surprises people. REST is a style, not a contract. REST tells you the shape of a call: verbs, addresses, headers, a body, a status. It doesn’t tell you that Steve Trucking’s deliveries live at /deliveries, or that a delivery has a depot. Every API has to write that down for itself.

This isn’t new. Before REST, there was SOAP, and SOAP had a contract file of its own.

A SOAP message is like an envelope. On the front of the envelope, the header: who it’s going to, and where it’s coming from. Inside the envelope, the body: everything you’re sending. And if something goes wrong, a fault, for the errors.

But to call something like get customer, you need to know what to pass in, and what you’ll get back. So the people who built SOAP came up with a file for that: the WSDL, the Web Services Description Language. Typically the server generates the WSDL and gives it to the client, and the client uses it to generate its own code. In Salesforce, you can still download the Enterprise WSDL from Setup and hand it to a team writing in Java or .NET. It’s a big file, and it has everything.

That’s the same idea we use today, in a newer form. SOAP and its WSDL have their own place in the story of integration, in chapter 16.

For REST, the contract is most often an OpenAPI file. You’ll also hear it called a Swagger file. Same thing, older name: “OpenAPI Specification (formerly Swagger Specification) is an API description format for REST APIs” (SmartBear, About OpenAPI (opens in a new tab), checked 2026-11-01). Today, Swagger is the name of a set of tools, and OpenAPI is the standard. The current version is OpenAPI 3.2.0, from September 2025 (OpenAPI Specification v3.2.0 (opens in a new tab), checked 2026-11-01).

An OpenAPI file is written in YAML or JSON, and it can look long. But reading one is easy, once you know it’s only answering five questions:

  1. Where is it? Under servers: the address of the server.
  2. What can I do? Under paths: the nouns, and under each one, the verbs it accepts.
  3. What do I send? Under each verb, the parameters and the requestBody.
  4. What comes back? Under responses: each status code, the good ones and the bad ones.
  5. What shape is it? Under components, the schemas: each kind of thing, described once and used everywhere.
Reading an OpenAPI file Five panels, one question each, with the part of the file that answers it. Where is it? servers: the name, which server, api.steve-trucking.example/v1. What can I do? paths and their verbs: the nouns, /deliveries/{id}; the verbs, get and patch. What do I send? parameters and requestBody: the id, in the path; the body, for a patch. What comes back? responses: 200, with the delivery; 404, and the error's shape. What shape is it? components, schemas: scalars, such as id, status and weight; structures, such as the recipient; and lists, such as the items. Underneath: five questions, five places; the good answers and the bad ones are both in the contract. {"author": "Steve Simpson", "blueprint": "1.0.0", "builder": "standard 1.52.0", "built": "2026-10-28", "id": "DGM-INT-AC-05", "rights": "\u00a9 2026 Twinshark LLC. All rights reserved.", "template": "comparison/side-by-side 0.5.0", "theme": "stevetecharc 1.4.0"} Where is it? servers The name: which server api.steve-trucking.example/v1 What can I do? paths, and their verbs The nouns: /deliveries/{id} The verbs: get, patch What do I send? parameters, requestBody The id, in the path The body, for a patch What comes back? responses 200, with the delivery 404, and the error's shape What shape is it? components: schemas Scalars: id, status, weight Structures: the recipient Lists: the items Five questions, five places. The good answers and the bad ones are both in the contract. © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

Reading an OpenAPI file: five questions, five places.

Here’s the contract for the two calls from the last module: get one delivery, and change its status.

openapi: 3.0.4
info:
title: Steve Trucking Deliveries
version: "1.0"
servers:
- url: https://api.steve-trucking.example/v1
paths:
/deliveries/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: string
get:
summary: Get one delivery
responses:
"200":
description: The delivery
content:
application/json:
schema:
$ref: "#/components/schemas/Delivery"
"404":
description: No delivery with that id
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
patch:
summary: Change part of a delivery
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum: [Planned, In transit, Delivered]
responses:
"200":
description: The changed delivery
content:
application/json:
schema:
$ref: "#/components/schemas/Delivery"
"400":
description: That status isn't allowed
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
schemas:
Delivery:
type: object
required: [id, status]
properties:
id:
type: string
status:
type: string
depot:
type: string
weightKg:
type: number
deliveredAt:
type: string
format: date-time
recipient:
type: object
properties:
name:
type: string
city:
type: string
items:
type: array
items:
type: object
properties:
sku:
type: string
quantity:
type: integer
Error:
type: object
properties:
code:
type: string
message:
type: string

Now read it with the five questions. Where? api.steve-trucking.example/v1. What can I do? On /deliveries/{id}, a get and a patch, and nothing else: send a delete, and it isn’t in the contract. What do I send? The id, in the path, for both; and for the patch, a body with a status, which can only be one of three values. What comes back? For the get, a 200 with a delivery, or a 404 with an error. For the patch, a 200 with the changed delivery, or a 400 if the status isn’t allowed. What shape? A Delivery and an Error, each described once at the bottom, and pointed to with $ref wherever they’re used. The next section opens the Delivery up.

The five questions tell you how to call. But they don’t yet tell you the most important thing: what are you actually getting back? Is it a customer? A delivery? What does it look like inside? That’s the job of the schemas. They’re the specification for the data: what goes in, and what comes back.

Open up the Delivery, and you’ll see three kinds of thing:

  • Scalars: a single value. id, status and depot are text (string). weightKg is a number. deliveredAt is text in a known format, a date and time. One field, one value.
  • Structures: a thing made of other things. The recipient is an object with its own name and city. It’s a little record inside the record.
  • Lists: many of something. The items are an array, and each one is an object with a sku and a quantity.

And required says which fields will always be there: every delivery has an id and a status; the rest may be missing.

Here’s a delivery that fits the schema, the way it comes back in the body:

{
"id": "D-1042",
"status": "Delivered",
"depot": "North",
"weightKg": 412.5,
"deliveredAt": "2026-11-01T14:05:00Z",
"recipient": { "name": "Corner Hardware", "city": "Springfield" },
"items": [
{ "sku": "PAL-200", "quantity": 3 },
{ "sku": "BOX-12", "quantity": 40 }
]
}

Scalars, structures and lists: almost every payload you’ll ever read is built from those three. This is only a taste. How that delivery is turned into text to cross the wire, and back again, is the next module. How the same delivery looks in XML, CSV and other formats is module 4. And describing and checking shapes like this one in depth, with JSON Schema and its cousins, is module 5.

Notice, too, that the errors are in the contract, with a shape of their own. That’s the server fulfilling the contract on a bad day: the client knows ahead of time what a 404 will look like, and can be built to handle it.

We wrote this file in OpenAPI 3.0, not the newest 3.2, on purpose. Many tools still read 3.0, and Salesforce’s External Services is one of them: it takes OpenAPI 2.0 and 3.0, in JSON or YAML (OpenAPI 2.0 and 3.0 Support (opens in a new tab), Salesforce Help, checked 2026-11-01). Chapter 4 brings this same file into Salesforce. And in module 6, AI drafts a file like this one from a sample payload, and you check it.

Look again at the address: /v1. Why is the version in the address at all?

Because functions change over time, and a client needs to know that what worked yesterday will work tomorrow. A version is a promise: this address will keep behaving the way it does now.

Salesforce is the best living example. Every Salesforce API address has a version in it: /services/data/v68.0/ is Winter ‘27. You don’t have to guess which versions an org serves. Ask it: a GET to /services/data/ returns the list. Our own guide, The API Guide (coming soon), shows that list from a real org, and every door it lists. And Salesforce commits to keeping each version functionally the same until it’s retired. Version 31 acts like version 31 until it stops working. It still sees new data, but it can’t see new data in a different way. Meanwhile, the newer versions do more. Compare version 31, from 2014, with today’s, and the newer one has many more APIs in it, working in a more powerful and efficient way. So you always want to build on a recent version.

But a promise doesn’t last forever. Salesforce retired versions 21.0 to 30.0 in Summer ‘25; calls to them now fail. Versions 31.0 to 40.0 are deprecated from Summer ‘27 and retired in Summer ‘28 (Salesforce Platform API Versions 31.0 through 40.0 Retirement (opens in a new tab), Salesforce Help, checked 2026-11-01). Every release, and the guides that came with it, is on our Salesforce Documents by Release page.

The life of an API version Three panels, the life of an API version on Salesforce. Supported, in green, the latest is 68.0, Winter '27: acts the same until it's retired; sees new data, the same way; newer versions do more. Deprecated, in amber, 31.0 to 40.0 from Summer '27: still works; time to find who calls it, and move them. Retired, in coral, 21.0 to 30.0 since Summer '25: calls fail; hard-coded addresses break. Underneath: Salesforce's dates, checked 2026-11-01; versions 31.0 to 40.0 retire in Summer '28. {"author": "Steve Simpson", "blueprint": "1.0.1", "builder": "standard 1.57.1", "built": "2026-11-01", "id": "DGM-INT-AC-06", "rights": "\u00a9 2026 Twinshark LLC. All rights reserved.", "template": "comparison/side-by-side 0.5.0", "theme": "stevetecharc 1.4.0"} Supported the latest: 68.0, Winter '27 Acts the same until it's retired Sees new data, the same way Newer versions do more Deprecated 31.0 to 40.0, from Summer '27 Still works Time to find who calls it And move them Retired 21.0 to 30.0, since Summer '25 Calls fail Hard-coded addresses break Salesforce's dates, checked 2026-11-01. Versions 31.0 to 40.0 retire in Summer '28. © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

The life of an API version. It keeps its promise until it’s retired.

That’s why an old version can be a time bomb. Somebody hard-coded a version into an external app years ago. It worked then, and it works today. Then the version retires, and the app stops. And the hard part isn’t the fix. It’s finding out who’s calling. These are outside clients calling in, so you have to go to the logs, find the callers, and trace each one back to a team that can update it. One student told the class about getting exactly that warning from Salesforce: requests were still coming in on versions about to be retired, and they had to go find them.

Salesforce versions its own APIs. It doesn’t version yours. Build a custom REST service in Apex, and it isn’t on Salesforce’s version schedule. It is you who’s releasing it, so it’s you who has to keep its promise.

That’s why Steve Trucking’s address has /v1 in it. When Steve Trucking needs to change what a delivery looks like in a way that would break its callers, it doesn’t change /v1. It publishes /v2, keeps /v1 working for a while, tells its callers when /v1 will retire, and keeps track of who still calls it. That’s the boundary from Owners, Boundaries and Change in chapter 1, written into an address.

  • For every API you call, where its contract is, and which version you’re building on.
  • For every API you offer, who owns its contract, and that the errors are in it, with their shapes, as well as the answers.
  • When a change breaks callers, and when it doesn’t: a new field usually doesn’t; a removed or renamed one does.
  • How you’ll know who’s calling an old version, before it retires.
  • For Salesforce, which API version each integration uses, and the date it has to move.

Beyond the exam: what AI changes. AI is very good at reading a long OpenAPI file and answering the five questions for you, and at drafting one from an API’s documentation. But the contract is a promise between owners. AI can draft it; a person has to agree to it, and keep it. Module 6 puts this to work.