
Learn how to build stateful mock APIs with Mockoon's setData helper using advanced API key rotation, usage tracking, login, and lockout scenarios.
Data buckets are often introduced as shared, persistent JSON stores for your routes. But they become much more powerful when you start updating them at runtime.
With the setData helper, you can turn a static mock API into a stateful system that reacts to previous requests: rotate API keys, count usage, track failed logins, create sessions, or lock accounts.
In this tutorial, we will focus on advanced data bucket manipulation with setData and build two realistic scenarios:
📘 If you are new to data buckets, start with our persisting data buckets tutorial first.
setData?The setData helper updates a data bucket while a request is being processed. This allows you to store state between calls and simulate systems that evolve over time.
It supports the following operations:
Copy
In practice:
set for replacing a single scalar or objectmerge for partial updates to an existing objectpush for logs, sessions, or append-only listsdel to remove temporary valuesinc and dec for counters, quotas, and retry trackinginvert to toggle flags such as enabled, locked, or maintenanceModesetData?You can use setData anywhere templating is supported, but the most common place is directly in a route response body.
Because setData returns an empty string, you can place one or more calls at the top of a response body template and then return the JSON payload you want your client to receive.
Like all data bucket changes, the new state persists until you restart the mock server.
This first example simulates an API gateway that can rotate keys, update metadata, disable access, and count authorized calls.
Create a data bucket named apiState with the following content:
Copy{ "currentKey": "sk_test_initial", "lastGeneratedKey": null, "enabled": true, "usageCount": 0, "revokedKeys": [], "auditLog": [] }
This bucket will hold the currently active key, some metadata, and an audit trail of administrative actions.
Create a route like POST /admin/api-key/rotate and use the following body:
Copy{ "message": "API key rotated" }
This single route performs several mutations in sequence:
lastGeneratedKey.revokedKeys.currentKey.usageCount to zero.After calling the route with curl -X POST http://localhost:3000/admin/api-key/rotate, you can check the live state of the bucket by clicking on the "Click to view current value" button below the data bucket editor:

You should see the updated data:

Now create a protected route like GET /private/products with two responses: a default 401 Unauthorized for missing or invalid keys, and a 200 that increments the counter when the correct key is provided.
📘 If you are new to responses and rules, check out our multiple responses tutorial first.
Set the first (default, with a blue flag) response to status code 401 with the following body:
Copy{ "error": "Missing or invalid API key" }
This response will be sent whenever no other rule matches, i.e. when the caller did not supply the right key.
Add a second response with status code 200. In the Rules tab, add a rule to check that the X-Api-Key request header matches the current key stored in the bucket:
HeaderX-Api-KeyEquals{{data 'apiState' 'currentKey'}}The value uses a custom templating rule: the data helper reads currentKey from the bucket at request time and compares it to the incoming header. This means the rule automatically validates against the most recent key, even after a rotation.
Set the response body to:
Copy[ { "id": 1, "name": "Keyboard" }, { "id": 2, "name": "Mouse" } ]
The setData calls run before the response is returned, so the counter is already incremented by the time the client receives the product list.
After calling this route with the correct key curl -H "X-Api-Key: <currentKey>" http://localhost:3000/private/products, you can check the bucket state again to see the updated usageCount and new entry in the auditLog.
This second example is a short, dependent workflow: each step changes the bucket state used by the next step.
Create a data bucket named loginState with the following content:
Copy{ "users": { "demoUser": { "email": "[email protected]", "password": "pa55word", "token": null, "failedAttempts": 0, "lastLoginAt": null } }, "loginEvents": [] }
Create a route like POST /auth/login with two responses:
401 Unauthorized (invalid credentials)200 OK (credentials are valid)Use this body for the default 401 response to keep track of failed attempts:
Copy{ "message": "Invalid credentials" }
Ensure the blue flag is on this response to make it the default one.
For the 200 response, add a rule that checks the request body password against the real password stored in the bucket:
BodypasswordEquals{{data 'loginState' 'users.demoUser.password'}}You can add the following body to the 200 response to generate a token and log a successful login:
Copy{ "message": "Login successful", "token": "" }
Now call the endpoint with a wrong password:
Copy$ curl -X POST http://localhost:3000/auth/login -H 'Content-Type: application/json' -d '{"password":"wrong"}'
Expected response:
Copy{ "message": "Invalid credentials" }
At this point, users.demoUser.failedAttempts should be 1.
Then call with the correct password from your bucket (pa55word in this example):
Copy$ curl -X POST http://localhost:3000/auth/login -H 'Content-Type: application/json' -d '{"password":"pa55word"}'
Expected result: the 200 response is selected by the rule, and a fresh token is generated, stored in users.demoUser.token, and returned in the response.
Create a route like GET /auth/me with two responses:
401 Unauthorized200 OK with a rule checking Authorization headerFor the default 401 response, use this body:
Copy{ "error": "Unauthorized" }
For the 200 response, add this rule:
HeaderAuthorizationEqualsBearer {{data 'loginState' 'users.demoUser.token'}}Use this response body for the 200 response:
Copy{ "email": "{{data 'loginState' 'users.demoUser.email'}}", "lastLoginAt": "{{dataRaw 'loginState' 'users.demoUser.lastLoginAt'}}" }
Now call the endpoint with the token returned by step 1:
Copy$ curl http://localhost:3000/auth/me -H "Authorization: Bearer <token-from-step-1>"
Expected 200 response includes the lastLoginAt stored during step 1. If the token is missing or wrong, the default 401 response is returned.
When building larger stateful mocks with setData, keep these rules in mind:
apiState, loginState, or billingState.merge when you want partial updates and set when you want to fully replace a value.push for logs, histories, and append-only collections.del as soon as they are no longer needed.Once you are comfortable with setData, you can combine it with other Mockoon features to create even richer simulations:
setData to store callback URLs and trigger them with dynamic data.You can download the example environment file created for this tutorial or directly open it in Mockoon desktop or CLI:
Learn how to build stateful mock APIs with Mockoon's setData helper using advanced API key rotation, usage tracking, login, and lockout scenarios.
Read moreLearn how to use Mockoon's CRUD routes to create a full mock REST API and manipulate resources with GET, POST, PUT, PATCH, and DELETE requests.
Read moreLearn how to simulate webhooks or callbacks in your mock API server to test your application's behavior when receiving asynchronous events from third-party services or APIs.
Read more