Recipes lab
Nine patterns you meet in real APIs, each built once in Mock Studio and tested once in ApiWorkbench. Every recipe is a folder in the collection, so you can run one, read how the mock does it, change it, and run it again.
Level: intermediate · Time: 10 minutes per recipe · Before this: Todo API lab
Download
- recipes.mockstudio.json — the mock, for Mock Studio
- recipes.apiworkbench.json — 15 requests in 9 folders, for ApiWorkbench
- signup-cases.csv — five sign-up cases for recipe 8
Set up
In Mock Studio, select Import, choose Mock Studio project file and pick
recipes.mockstudio.json. Select Start server. The mock is athttp://localhost:4040.In ApiWorkbench, select Import, choose
recipes.apiworkbench.jsonand select Import.Run the whole collection once from its ⋯ menu with Run collection. All 17 sends should pass. (There are 15 requests; the polling request sends itself three times.)
| # | Recipe | The pattern | Mock Studio feature | ApiWorkbench feature |
|---|---|---|---|---|
| 1 | Paging | Follow the next link | Rules on a query parameter | Capture a link, use it as the URL |
| 2 | Echo | Put request data in the answer | Templates: {{body.name}}, {{uuid}} | JSON field tests |
| 3 | ETag | Ask only if it changed | Rule on a header, a 304 response | Capture a header, send it back |
| 4 | Versioning | Pick a version by header | Rule on a header | Request headers |
| 5 | Token refresh | Recover from an expired token | Rules in order | Bearer auth, captures |
| 6 | Webhooks | Be the receiver | A plain 204, the Live log | Dynamic variables |
| 7 | Polling | Ask until it is done | Sequence | A script with setNextRequest |
| 8 | Validation | One request, every rule | Regex rules | A data file and a script |
| 9 | CORS | The browser's preflight | CORS switch | OPTIONS and header tests |
1. Paging: follow the next link
The pattern. Lists come a page at a time. A good API tells you where the next page is, and a good client follows that link instead of building URLs itself.
The mock. List articles (paged) uses Conditional rules on the query parameter page: 2 and 3 get their own responses, anything else gets page 1. Each page includes next; the last page has "next": null.
The tests.
| Request | Does | Checks |
|---|---|---|
| First page | GET /articles, captures nextPage = body.next | page is 1, first id is 1 |
| Follow the next link | GET {{baseUrl}}{{nextPage}} | page is 2, first id is 3 |
| Past the last page | GET /articles?page=3 | next is null, items is empty |
The second request has no page number written in it: the URL comes from the first response. If the API changes how it pages, this test keeps working.
Try it. Add a page 4 rule in the mock and change page 3's next to point at it. Run the folder: nothing in ApiWorkbench needs to change for the second request.
2. Echo: put request data into the response
The pattern. A create endpoint usually returns what you sent, plus what the server added: an id and a timestamp.
The mock. Echo answers POST /echo with templates:
{
"hello": "{{body.name}}",
"city": "{{body.city}}",
"requestId": "{{uuid}}",
"receivedAt": "{{now}}"
}The tests. Send {"name": "Ada", "city": "Chennai"} and check that hello is Ada and city is Chennai. requestId changes every time, so the test checks only that it is there (Body contains requestId), never its value.
Try it. Send a body without city. The mock leaves {{body.city}} as written, which is how Mock Studio shows a token it could not fill. See Dynamic templating.
3. ETag: ask only if it changed
The pattern. The server labels a resource with an ETag. The client sends that label back in If-None-Match; if nothing changed, the server answers 304 Not Modified with no body, and the client keeps its saved copy. Less data, faster pages.
The mock. Profile (ETag) has two responses. With the rule Header If-None-Match equals "v7", it sends 304 and no body. Otherwise it sends the profile with the header ETag: "v7".
The tests.
| Request | Does | Checks |
|---|---|---|
| First fetch | GET /profile, captures etag = header.ETag | 200, ETag contains v7 |
| Fetch again with If-None-Match | Sends If-None-Match: {{etag}} | 304 |
The quotes are part of the ETag. The capture keeps them, so the value sent back matches exactly.
Try it. In the mock, change every "v7" to "v8", as a server would after an edit, and run the folder. The second request still gets 304, because it sends back whatever the first one captured. Only the first request's ETag contains v7 test fails: it checks a fixed label, which is exactly what changed.
4. Versioning: pick a version with a header
The pattern. When an API changes the shape of a response, old clients must keep working. One common way: the new shape only for clients that ask for it, here with Accept-Version: 2.
The mock. Greeting (versioned) has a rule on the header Accept-Version. Version 1 says "message": "Hello"; version 2 says "message": { "text": "Hello", "lang": "en" }.
The tests. One request without the header checks message is Hello. One with the header checks message.text is Hello. Together they prove version 2 exists and version 1 still works, which is the promise a versioned API makes.
5. Token refresh: recover from an expired token
The pattern. Access tokens expire. The client sees a 401, trades its refresh token for a new access token, and tries again.
The mock. Secure report checks its rules in order:
| Order | Condition | Answer |
|---|---|---|
| 1 | Authorization equals Bearer expired-token | 401 token_expired, with a WWW-Authenticate header |
| 2 | Authorization exists | 200, the report |
| 3 | (fallback) | 401 missing_token |
Refresh token gives a new accessToken for the refresh token refresh-123, and 401 for anything else.
The tests. Call with the expired token and expect 401. Call Get a new token, which captures accessToken = body.accessToken. Retry with Bearer Token {{accessToken}} and expect 200.
Rule order matters: if rule 2 came first, the expired token would match "exists" and get the report. See Conditional rules.
Try it. Point your own app at the mock with the token expired-token. Does it refresh and retry by itself, or show the user an error?
6. Webhooks: be the receiver
The pattern. A webhook is a request a service sends to you when something happens: a payment succeeded, a build finished. To build the receiver, you need to see exactly what arrives.
The mock. Payment webhook simply answers 204 to POST /webhooks/payments. The useful part is Mock Studio's Live log, which shows the headers and body of each event.
The tests. ApiWorkbench plays the sending service: a POST with an X-Signature header and an event body. {{$randomInt}} gives each event a different id.
{
"event": "payment.succeeded",
"id": "evt_{{$randomInt}}",
"amount": 1999,
"currency": "INR"
}Send it, then open Live in Mock Studio and select the request to read it.
Try it. To receive webhooks from a real service on the internet, the mock needs a public address. See Public internet (tunnel), and stop the tunnel when you are done.
7. Polling: ask until it is done
The pattern. Slow work starts a job and returns at once. The client asks for its status until it says done.
The mock. Job status uses Sequence (round-robin): queued, then running, then done, then round again.
The tests. Wait for the job to finish has a post-response script that sends the same request again until the status is done, at most 10 times:
const body = pm.response.json();
const tries = Number(pm.collectionVariables.get('pollTries') || 0) + 1;
pm.collectionVariables.set('pollTries', String(tries));
if (body.status !== 'done' && tries < 10) {
postman.setNextRequest('Wait for the job to finish');
} else {
pm.collectionVariables.set('pollTries', '0');
pm.test('job finished within 10 tries', function () {
pm.expect(body.status).to.eql('done');
});
}postman.setNextRequest works in a run, so use Run folder on Polling. The results show the request three times, and the last one passes the test. Always put a limit on a loop like this: a job that never finishes should fail the test, not hang the run. See Script cookbook.
8. Validation: one request, every rule
The pattern. A sign-up form has rules: a real-looking email, a password of at least 8 characters. Each broken rule needs its own clear error.
The mock. Sign up uses matches regex conditions, checked top to bottom:
| Order | Conditions | Answer |
|---|---|---|
| 1 | email matches ^[^@\s]+@[^@\s]+\.[^@\s]+$ and password matches ^.{8,}$ | 201 |
| 2 | email matches that pattern and password exists | 422, field: password |
| 3 | email exists | 422, field: email |
| 4 | (fallback) | 400, missing fields |
Mock Studio has no "does not match", so the rules run from the most valid to the least: anything that falls past rule 1 is broken in some way.
The tests. One request, {"email": "{{email}}", "password": "{{password}}"}, run once per row of signup-cases.csv:
email,password,expectedStatus,expectedField
ada@example.com,correct-horse,201,
ada@example.com,short,422,password
not-an-email,correct-horse,422,email
ada@example,correct-horse,422,email
,correct-horse,400,The post-response script reads expectedStatus and expectedField from the row and checks the response against them.
Open the Validation folder's ⋯ menu and choose Run folder.
Select Load data file and pick
signup-cases.csv.Select Run. Five passes, one per row.
Try it. Add the row ada@@example.com,correct-horse,422,email. Does the regex catch it? Then add a rule to the mock that refuses passwords made only of digits, and a row to prove it. See Data-driven testing.
9. CORS: the browser's preflight
The pattern. Before a web page on one site calls an API on another, the browser asks permission with an OPTIONS request, the preflight. If the API's answer does not allow it, the browser blocks the call, however correct the API is.
The mock. Nothing to build: with CORS on in the project settings (the default), Mock Studio answers every preflight with 204 and the Access-Control-Allow-… headers. See Auth, CORS and project settings.
The tests. Preflight check sends OPTIONS /articles with an Origin and an Access-Control-Request-Method: POST header, as a browser would, and checks the status and both Access-Control-Allow-… headers.
Try it. Turn CORS off in the project settings and run the folder again. The preflight no longer gets the CORS headers, and the header tests fail: that is what a browser would block.
Check yourself
Question 1 of 6
Page 1 of a list answers with this. What should the client request next?
{ "page": 1, "items": [...], "next": "/articles?page=2" }What you learned
- Follow links the API gives you; capture them instead of building URLs.
- Template tokens make a mock answer with the data you sent.
- Rule order decides which response wins; put the most specific first.
- A sequence makes retries and polling testable; a script can loop, with a limit.
- One request plus a data file can prove every validation rule.