ShiftLeft Store API Learning Sandbox

A live, multi-protocol API you can hit right now — no setup, no login required.

This sandbox simulates a real online store (customers, products, orders) exposed simultaneously over REST, GraphQL, and SOAP — all sharing one live data store. Use it to practise API testing, explore protocols side-by-side, or demonstrate tools like Shift‑Left Studio without any backend setup.

Session-scoped · auto-expires 10 min
🔒 JWT · API Key · OAuth2
⚡ REST · ⬡ GraphQL · 📄 SOAP
✨ Want to learn even faster?
Get a Free Shift-Left Studio Developer Account
Build no-code API tests against this sandbox — guided tutorials, real test runs, and a full toolkit for free.
Sign up free →
Recommended Learning Path
📋
STEP 1
Read the Requirements
Understand what the Store API does, what each field expects, and what errors to anticipate — before you write a single test.
Open Requirements →
STEP 2
Explore the REST API
Try all CRUD endpoints for users, products, and orders. Send real requests, see real responses — right in the browser.
Open REST →
🔑
STEP 3
Add Authentication
Log in with JWT, use a static API key, or try OAuth2 client credentials. Test all three methods against the same endpoints.
Open Auth →
STEP 4
Try GraphQL
Query the same store data with GraphQL. Fetch exactly the fields you need, resolve nested entities in one call, compare with REST.
Open GraphQL →
📄
STEP 5
Explore SOAP
14 SOAP operations with a complete WSDL. The same users and orders you created via REST appear here instantly — one shared data store.
Open SOAP →
📚
GUIDED
Guided Learning Scenarios
Step-by-step exercises from beginner to advanced. Each scenario explains the why behind every API call — not just the how.
Open Scenarios →
🎲
ADVANCED
Simulate Errors & Delays
Force HTTP errors, add artificial latency, or trigger ~30% random failures. Test how your tool or app handles the real world.
Open Simulate →
📖
TOOL
Swagger UI
Full OpenAPI 3.0 spec with try-it-out on every endpoint. Great for importing into Postman or generating client SDKs.
Open Swagger ↗
TOOL
GraphiQL IDE
Full GraphQL IDE with schema autocomplete, query history, and inline docs. The fastest way to explore the GraphQL schema.
Open GraphiQL ↗
Quick Facts
Base URL
https://demo.totalshiftleft.ai
Session Header
x-session-id: <id>
Demo Credentials
alice@demo.com / alice123
API Key
demo-key-sandbox-2024
Data Lifetime
10 min inactivity
Rate Limit
100 req / 60 sec

REST API — Users

Full CRUD for users, products, and orders. Data is session-scoped and auto-expires in 10 min.

👤 users 📦 products 🛒 orders
Endpoint Reference 📖 Full Swagger Docs ↗ ⬇ Download OpenAPI Spec

GraphQL API

Full CRUD over users, products, and orders via GraphQL. Shares the same session store as REST and SOAP.

🔗

Endpoint

POST /graphql — send JSON { "query": "..." }

GraphiQL IDE

Interactive explorer at /graphiql — schema browser built in

📄

SDL Schema

Full schema at /graphql — equivalent of /docs/json

🔄

Cross-protocol

Records created via REST or SOAP appear instantly in GraphQL queries

Query / Mutation
Response
// Run a query to see the response

Schema Reference

Queries
users(page, limit, sort, order)UserPage
user(id)User
products(page, limit, sort, order)ProductPage
product(id)Product
orders(page, limit, sort, order)OrderPage
order(id)Order — includes user & product inline
sessionInfoJSON
Mutations
createUser(input) / updateUser(id, input) / deleteUser(id)
createProduct(input) / updateProduct(id, input) / deleteProduct(id)
createOrder(input) / updateOrder(id, input) / deleteOrder(id)
Types
User — id, name, email, role, age, created_at, updated_at
Product — id, name, price, description, stock, category, created_at, updated_at
Order — id, user_id, product_id, quantity, status, notes, created_at, updated_at, user, product
*Page — items[], pageInfo { total, page, limit, pages, hasNext, hasPrev }
Key Concepts
Nested resolution: Query order { user { name } product { price } } — linked entities resolved automatically
Pagination: All list queries accept page, limit, sort, order
Session: Send x-session-id header to share data with REST and SOAP
Full SDL: /graphql — import into any GraphQL client

SOAP API

Full CRUD over users, products, and orders via SOAP 1.1. 14 operations with a complete WSDL.

📄

WSDL

Service definition at /soap?wsdl — import into SoapUI or Postman

📮

Endpoint

POST /soap · Content-Type: text/xml · SOAPAction header required

⚠️

Errors

Errors return <soap:Fault> · <faultcode> + <faultstring>

Authentication

Three auth methods supported. Configure in the sidebar, or try each one below.

Learning Scenarios

Guided step-by-step exercises from beginner to advanced. Each scenario explains the why, not just the how.

Error & Delay Simulation

Add query params to any REST endpoint to test client-side error handling and resilience.

⏱ Artificial Delay

Simulate network latency or slow backends. Max 10,000 ms.

?delay=N
Delay (ms)

💥 Force HTTP Error

Force any HTTP error code. Useful for testing your UI error states.

?error=N
HTTP Status Code

🎲 Random Failure

~30% of requests will randomly fail. Build retry logic and test resilience.

?random_fail=true

Click multiple times — about 1 in 3 will fail.

🔗 Chain Params

Combine simulation params on any endpoint.

?delay=1000&random_fail=true
Entity
Custom params
ShiftLeft Store API Functional Requirements & Use Cases · v1.0 ·
1 Introduction

This document defines the functional requirements and use cases for the ShiftLeft Store API — a multi-protocol online store simulation built for API testing training and tool demonstrations. The store exposes customer registration, product catalogue management, and order processing across three API protocols (REST, GraphQL, SOAP) simultaneously, all sharing one session-scoped data store.

The system is intentionally stateless between sessions. All data auto-expires after 30 minutes of inactivity. This makes it safe to use in live demonstrations: every attendee or trainee gets a clean, isolated environment without database setup.

💡 Session scoping: Your session ID is returned in the x-session-id response header on every call. Include it in subsequent requests to keep your data. Data created by one participant cannot be seen by another.
🛒 Shopper (Customer)
  • Register an account
  • Browse the product catalogue
  • View a specific product
  • Place an order
  • View order confirmation with details
🏪 Store Manager
  • Add products to the catalogue
  • Update product details and stock
  • View and filter all orders
  • Accept and process orders
  • Mark orders complete or cancelled
💻 API Tester / Developer
  • Test REST, GraphQL, and SOAP
  • Verify cross-protocol data consistency
  • Test auth: JWT, API Key, OAuth2
  • Simulate errors, delays, random failures
  • Run automated end-to-end scenarios
2 Functional Requirements
2.1   Shopper Requirements
FR-001
Customer Registration
"As a shopper, I want to create an account with my name and email so the store can identify me when I place orders."
POST /api/v1/usersname · email requiredrole · age optional
Input:name (string, 1–100 chars), email (valid format, max 255), role (user|admin|moderator, default: user), age (integer 0–150)
Output:Full customer object with system-generated UUID, created_at timestamp
Accepts:✓ Creates profile and returns it with a unique ID for use in future orders
Rejects:✗ Missing name → 400 VALIDATION_ERROR  ·  Invalid email format → 400  ·  Session at 500 object cap → 400 LIMIT_EXCEEDED
FR-002
Browse Product Catalogue
"As a shopper, I want to see all available products with their names, prices, and stock levels so I can decide what to buy."
GET /api/v1/productsall params optional
Input:page (default 1), limit (1–100, default 10), sort (field name, default created_at), order (asc|desc, default desc), any field filter e.g. ?category=electronics
Output:Paginated list of products + pagination metadata (total, page, pages, hasNext, hasPrev)
Accepts:✓ Returns empty list (not an error) when no products exist yet
Accepts:✓ ?sort=price&order=asc returns cheapest first  ·  ?category=electronics filters by category field
FR-003
View a Single Product
"As a shopper, I want to see the full details of a specific product — description, stock level, and exact price — before I commit to buying it."
GET /api/v1/products/{id}
Input:id — UUID of the product (from the list endpoint or a previous create)
Output:Single product object: id, name, price, description, stock, category, created_at, updated_at
Accepts:✓ Returns the full product if it exists in the current session
Rejects:✗ Unknown ID → 404 NOT_FOUND  ·  Malformed UUID → 400 VALIDATION_ERROR
FR-004
Place an Order
"As a shopper, I want to place an order for a product I have chosen, linked to my customer account, so the store knows what to prepare and who to send it to."
POST /api/v1/ordersuser_id · product_id · quantity · status · notes all optional
Input:user_id (UUID of customer), product_id (UUID of product), quantity (integer > 0, default 1), status (pending|processing|completed|cancelled, default pending), notes (string max 500)
Output:Created order object with id, user_id, product_id, quantity, status, notes, created_at
Accepts:✓ user_id and product_id are optional — the order is valid even without them (useful for testing)
Accepts:✓ When user_id and product_id are provided, the order links to real customer and product records in the session
Rejects:✗ quantity = 0 → 400 VALIDATION_ERROR  ·  Invalid status value → 400
FR-005
View Order Confirmation with Full Details
"As a shopper, I want to see my order confirmation with the product name, price, and my own customer details all in one response — without making multiple separate calls."
GET /api/v1/orders/{id}?expand=user,product
Input:id (order UUID), expand query param: "user", "product", or "user,product"
Output:Order object with linked user and/or product objects embedded inline under "user" and "product" keys
Accepts:✓ ?expand=user,product returns the order with both the full customer profile and the full product details in a single API call
Accepts:✓ If the linked customer or product was deleted, the expanded field returns null — the order itself is not affected
Rejects:✗ Unknown order ID → 404 NOT_FOUND
2.2   Store Manager Requirements
FR-006
Add a Product to the Catalogue
"As a store manager, I want to add new products with a name, price, description, and stock level so shoppers can find and buy them."
POST /api/v1/productsname · price requireddescription · stock · category optional
Input:name (1–100 chars), price (float, must be > 0), description (max 500, default ""), stock (integer ≥ 0, default 0), category (max 50, default "general")
Output:Created product with UUID, all fields, and created_at
Accepts:✓ Returns the full product object immediately — ID is ready to use in orders
Rejects:✗ price ≤ 0 → 400  ·  Missing name → 400  ·  stock < 0 → 400
FR-007
Update Product Details (Partial)
"As a store manager, I want to update the price or stock of a product without having to resend all its other details."
PATCH /api/v1/products/{id}any field optional
Input:Any subset of product fields. Unspecified fields are preserved exactly as they are.
Output:Updated product object with new updated_at timestamp
Accepts:✓ PATCH { "price": 79.99 } changes only the price — name, stock, category remain unchanged
Accepts:✓ PUT replaces the entire product — all required fields must be present. Use PUT when migrating or bulk-updating.
Rejects:✗ Unknown product ID → 404  ·  Field fails validation (e.g. price: -5) → 400
FR-008
View and Filter All Orders
"As a store manager, I want to list all orders and filter them by status so I can quickly see what needs my attention."
GET /api/v1/orderspage · limit · sort · order · status filter optional
Input:Standard pagination params. Any field filter: ?status=pending, ?user_id=uuid, ?status=processing
Output:Paginated orders matching the filters, with pagination metadata
Accepts:✓ ?status=pending returns only unprocessed orders — ideal for a manager dashboard
Accepts:✓ ?sort=created_at&order=asc shows oldest orders first — useful for first-in-first-out processing
FR-009
Process an Order — Status Lifecycle
"As a store manager, I want to update an order's status as I handle it — from pending, to processing, to completed — so the customer can track progress."
PATCH /api/v1/orders/{id}{ "status": "..." }
Input:{ "status": "processing" } or "completed" or "cancelled". Other fields can be updated simultaneously.
Output:Updated order with new status and updated_at timestamp
Status flow:✓ pending → processing → completed  ·  Any stage → cancelled
Rejects:✗ Any status value outside the allowed enum → 400 VALIDATION_ERROR
Note:The system does not enforce status transitions — you can move from "pending" directly to "completed" if desired.
FR-010
Remove a Product from Catalogue
"As a store manager, I want to remove a product that is discontinued or out of stock permanently so it no longer appears in the catalogue."
DELETE /api/v1/products/{id}
Input:id — UUID of the product to remove
Output:204 No Content — no body. Absence of error means success.
Accepts:✓ Permanently removes the product from the session
Note:Orders referencing the deleted product are not removed. The ?expand=product field on those orders returns null.
Rejects:✗ Unknown product ID → 404 NOT_FOUND
2.3   Authentication Requirements
FR-011
Login and Receive a JWT Token
"As a registered user, I want to log in with my email and password and receive a secure token I can use to prove my identity on protected routes."
POST /auth/tokenusername · password required
Input:{ "username": "alice@demo.com", "password": "alice123" }
Output:{ "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 600 }
Demo users:alice@demo.com / alice123 (admin)  ·  bob@demo.com / bob123 (user)  ·  charlie@demo.com / charlie123 (moderator)
Usage:✓ Send token as: Authorization: Bearer <token> on any request
Rejects:✗ Wrong credentials → 401 UNAUTHORIZED  ·  Missing fields → 400
FR-012
Verify Identity (Who Am I?)
"As an authenticated user, I want to call an endpoint that tells me who I am so I can confirm my token or API key is being read correctly."
GET /auth/me
Input:Authorization: Bearer <token> header, OR x-api-key: demo-key-sandbox-2024 header
Output:{ "authenticated": true, "method": "jwt"|"apikey", "identity": { "sub": "alice@demo.com", "role": "admin" } }
API Keys:✓ demo-key-sandbox-2024 (user role)  ·  admin-key-sandbox-2024 (admin role)
No auth:Returns { "authenticated": false } — not an error, just unauthenticated
FR-013
Machine-to-Machine Auth (OAuth2 Client Credentials)
"As a backend service, I want to authenticate without a user login, using a client ID and secret, so I can call the API programmatically."
POST /auth/oauthgrant_type · client_id · client_secret
Input:{ "grant_type": "client_credentials", "client_id": "sandbox-client", "client_secret": "sandbox-secret", "scope": "read write" }
Output:{ "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 600, "scope": "read write" }
Rejects:✗ Wrong client_secret → 401  ·  grant_type not "client_credentials" → 400
2.4   Multi-Protocol & Resilience Requirements
FR-014
Query Store Data via GraphQL
"As a developer, I want to query the store data using GraphQL so I can fetch exactly the fields I need in one request, including linked entities."
POST /graphql
Input:JSON body: { "query": "..." } — standard GraphQL query or mutation syntax
Entities:users (+ user), products (+ product), orders (+ order) — all with pagination and mutations
Expansion:✓ query { order(id: "uuid") { id user { name email } product { name price } } } — linked entities inline
Cross-protocol:✓ A user created via REST is immediately visible in GraphQL and vice versa. Same session store.
FR-015
Create and List Users via SOAP
"As a legacy system integrator, I want to create users and retrieve them using SOAP XML so I can test my XML-based tooling against the same store data."
POST /soapSOAPAction: CreateUser | GetUsers
WSDL:GET /soap?wsdl — the service definition for importing into SoapUI or similar tools
Operations:CreateUser (name, email, role) · GetUsers (page, limit)
Cross-protocol:✓ Users created via SOAP are visible in REST and GraphQL. All protocols share the same session.
FR-016
Simulate API Failures for Resilience Testing
"As a tester, I want to deliberately cause errors, slow responses, and random failures so I can verify that my tool or application handles them correctly."
Any REST endpoint + query params
?delay=NWaits N milliseconds before responding. Max 10,000. E.g. ?delay=3000 simulates a 3-second network lag.
?error=NForces a specific HTTP error code. E.g. ?error=503 simulates a down server.
?random_fail=true~30% chance of a 500 error on each call. Ideal for testing retry logic and exponential backoff.
Combine:✓ ?delay=1000&random_fail=true — slow AND flaky, for realistic worst-case simulation
3 Use Cases

Detailed use cases showing pre-conditions, step-by-step flows, alternative paths, and expected outcomes for each key interaction.

UC-001 Customer Places First Order Actor: Shopper
Goal
A new customer registers, finds a product, and successfully places an order.
Preconditions
At least one product exists in the session (run the Seed Store scenario, or POST /api/v1/products first).
Main Flow
  1. 1Customer sends POST /api/v1/users with name and email. System returns a customer object including a unique id.
  2. 2Customer sends GET /api/v1/products?sort=price&order=asc. System returns the full product list sorted cheapest first. Customer notes the id of the chosen product.
  3. 3Customer sends POST /api/v1/orders with user_id (from step 1) and product_id (from step 2). System creates the order with status "pending" and returns the order object.
  4. 4Customer sends GET /api/v1/orders/{id}?expand=user,product. System returns the order with the full customer profile and product details merged inline.
Postconditions
An order exists in the session with status "pending", linked to the customer and product by UUID. The order ID can be used by the manager to process it.
Alternative Flows
A1: No products exist → GET /api/v1/products returns empty list. Customer must notify manager to add products first. A2: Customer provides wrong email format → POST /api/v1/users returns 400 VALIDATION_ERROR with details: [{ field: "email", message: "Invalid email" }]. Customer corrects and retries. A3: Customer uses product_id from a different session → Order is created but the ?expand will return product: null (foreign ID not found in this session).
Run Live
UC-002 Manager Processes and Fulfils an Order Actor: Store Manager
Goal
Manager reviews a new pending order, accepts it, and marks it complete when the goods are dispatched.
Preconditions
At least one order exists with status "pending" in the session.
Main Flow
  1. 1Manager sends GET /api/v1/orders?status=pending&sort=created_at&order=asc. System returns the oldest unprocessed orders first.
  2. 2Manager picks an order and sends GET /api/v1/orders/{id}?expand=user,product to see the full customer and product details.
  3. 3Manager sends PATCH /api/v1/orders/{id} with { "status": "processing" }. System updates the order and returns it with updated_at set.
  4. 4After goods are dispatched, manager sends PATCH /api/v1/orders/{id} with { "status": "completed" }. System records the final state.
Postconditions
The order status is "completed" with an updated_at timestamp. The order remains in the session until it expires.
Alternative Flows
A1: Manager decides to reject the order → PATCH { "status": "cancelled" }. The order is marked cancelled and no further status changes are required. A2: Order not found → 404 NOT_FOUND. Manager checks the order ID is correct and belongs to the current session. A3: Invalid status value → 400 VALIDATION_ERROR. Only: pending, processing, completed, cancelled are accepted.
Run Live
UC-003 User Authenticates and Verifies Identity Actor: Any authenticated user
Goal
User logs in, receives a JWT, and confirms the token works by calling the identity endpoint.
Preconditions
None. Any demo user credential will work.
Main Flow
  1. 1User sends POST /auth/token with { "username": "alice@demo.com", "password": "alice123" }. System returns access_token (JWT).
  2. 2User copies the access_token and sends GET /auth/me with header: Authorization: Bearer <token>. System returns identity: { sub, role }.
  3. 3Optional: user sends POST /auth/verify with { "token": "<token>" }. System confirms validity and returns the full JWT claims including expiry.
Postconditions
User has a valid token they can use on any request. Token expires after 600 seconds.
Alternative Flows
A1: Wrong password → POST /auth/token returns 401 UNAUTHORIZED. A2: API Key instead of JWT → send x-api-key: demo-key-sandbox-2024 header to GET /auth/me. Returns method: "apikey". A3: OAuth2 flow → POST /auth/oauth with client_id: "sandbox-client", client_secret: "sandbox-secret". Returns the same token shape as JWT login — usable on GET /auth/me.
Run Live
UC-004 Developer Verifies Cross-Protocol Data Consistency Actor: API Tester / Developer
Goal
Prove that REST, GraphQL, and SOAP all read and write the same underlying session store — a record created in one protocol is immediately visible in the others.
Preconditions
None. A clean session works.
Main Flow
  1. 1Tester sends POST /api/v1/users (REST). Notes the returned user id.
  2. 2Tester opens /graphql and runs: query { user(id: "<id>") { id name email } }. The same user appears — created by REST, read by GraphQL.
  3. 3Tester sends a SOAP CreateUser request (POST /soap, SOAPAction: CreateUser). The new user is returned.
  4. 4Tester runs GET /api/v1/users via REST. Both the REST-created and SOAP-created users appear in the list.
  5. 5Tester sends DELETE /api/v1/users/{id} via REST. Re-running the GraphQL query returns null — the deletion is reflected everywhere.
Postconditions
Tester has confirmed that the three protocols are not isolated — they are alternative interfaces to the same data store.
Alternative Flows
A1: Session expired → all data gone, start fresh. Sessions auto-expire after 10 min of inactivity. A2: User queries GraphQL with an ID from a different session → user(id) returns null (not found in this session). This is expected behaviour.
Run Live
UC-005 Tester Validates Error Handling Actor: API Tester
Goal
Confirm that the testing tool or application under test handles HTTP errors, timeouts, and flaky responses gracefully.
Preconditions
None. Simulation params work on any REST endpoint.
Main Flow
  1. 1GET /api/v1/users?error=500 — server returns 500 SERVER_ERROR. Tool should show an error state, not crash.
  2. 2GET /api/v1/users?error=404 — server returns 404 NOT_FOUND. Tool should handle "not found" gracefully.
  3. 3GET /api/v1/users?delay=5000 — server waits 5 seconds. Tool should show a loading indicator and not time out prematurely.
  4. 4POST /api/v1/users with missing "name" field — server returns 400 VALIDATION_ERROR with details array. Tool should surface the field-level error message.
  5. 5GET /api/v1/users?random_fail=true — call 10 times. ~3 should fail with 500. Tool should retry failed calls automatically.
Postconditions
Tester has evidence that the tool under test handles each class of error without data loss or crash.
Expected Error Format
All errors follow this shape: { "success": false, "error": "ERROR_CODE", "message": "Human-readable explanation", "details": [ ← only on 400 { "field": "name", "message": "Required" } ] }
4 API Quick Reference

All REST endpoints follow /api/v1/{entity} where entity is users, products, or orders. Session ID must be sent as x-session-id header to maintain your data across calls.

MethodPathWhat it does
POST/api/v1/usersRegister a customer — requires name, email
GET/api/v1/usersList customers — ?page ?limit ?sort ?order ?role=admin
GET/api/v1/users/{id}Fetch one customer by UUID
PATCH/api/v1/users/{id}Partial update — send only changed fields
PUT/api/v1/users/{id}Full replacement — all required fields must be present
DELETE/api/v1/users/{id}Remove customer — returns 204 No Content
— same pattern for /products and /orders —
GET/api/v1/orders/{id}?expand=user,productOrder with customer + product merged inline
POST/auth/tokenLogin — returns JWT. Body: { username, password }
GET/auth/meWho am I? Works with Bearer token or x-api-key header
POST/auth/oauthOAuth2 client credentials grant
POST/graphqlGraphQL queries & mutations — send { "query": "..." }
POST/soapSOAP 1.1 — SOAPAction: CreateUser | GetUsers
GET/soap?wsdlDownload the WSDL service definition
POST/api/v1/flows/seed-storeSeed session with 4 demo products
POST/api/v1/flows/customer-journeyFull purchase flow in one call
POST/api/v1/flows/process-orderOrder status lifecycle in one call
5 Error Reference

Every error response has the same JSON shape: { "success": false, "error": "CODE", "message": "..." }. Validation errors (400) additionally include a details array with one object per broken field: { "field": "email", "message": "Invalid email" }.

200
OK — Request succeeded
Response body contains your data under the data key alongside success: true.
201
Created — Record was created
The full created object is returned. The generated id is safe to use in subsequent calls immediately.
204
No Content — Delete succeeded
No response body. The absence of an error confirms the deletion worked. Do not treat an empty body as a failure.
400
Bad Request — Validation failed or limit exceeded
A required field is missing, a value is the wrong type, or outside allowed range. Check the details array for field-by-field breakdown. Also returned when session is at the 500-object cap (error: LIMIT_EXCEEDED).
✓ Fix: add the missing field, correct the value, or delete old records to free capacity
401
Unauthorized — Authentication failed
Wrong username/password on POST /auth/token, or an expired/invalid token sent to a protected route.
✓ Fix: re-authenticate using correct demo credentials: alice@demo.com / alice123
404
Not Found — Record does not exist in this session
The ID you referenced was not found. Most common cause: using an ID from a different session, or the record was deleted.
✓ Fix: GET /api/v1/{entity} to retrieve valid IDs for the current session
429
Rate Limited — Too many requests
More than 100 requests in 60 seconds from the same IP. The response includes a Retry-After hint.
✓ Fix: add a delay between requests, or use the ?delay param to pace automated tests
500
Server Error — Unexpected failure
An unhandled exception occurred. Can be intentionally triggered with ?error=500 for testing your error-handling logic.
✓ Fix: retry the request — if using ?error=500 intentionally, that is the expected behaviour
503
Service Unavailable — System at capacity
The sandbox has reached the maximum number of active sessions. Rare in normal use.
✓ Fix: wait a few minutes for sessions to expire and try again
6 Test Requirements & Sample Data

This section is formatted as a ready-to-upload requirements document for Shift-Left Studio. Upload this page (Download as Word or Text above) into the Rule Intelligence tab and the AI will automatically extract every constraint, business rule, and validation into individual test cases. Each block below follows the pattern: what the field is, what values are allowed, what must be rejected, and a sample data set you can use immediately.

💡 How to use this as a template: When writing requirements for your own API, follow the same pattern — one block per field or rule, with explicit accept/reject examples and sample values. The more specific you are here, the richer the generated test suite will be.
6.1   Users Entity — Field Constraints & Business Rules
RULE
U-01
name — Required string, 1 to 100 characters
Every user account must have a display name. The name is used on order confirmations and in SOAP responses. It cannot be blank or exceed 100 characters.
Field:name · Type: string · Required: YES · Min length: 1 · Max length: 100
Valid values:✓ "Alice Johnson"  ·  "Bob"  ·  "María García"  ·  Any printable string up to 100 chars
Invalid values:✗ "" (empty string) → 400 VALIDATION_ERROR, field: name, message: "name is required"  ·  missing field entirely → same error  ·  string of 101 chars → 400
Default:None — field is required, no default applies
RULE
U-02
email — Required, valid email format, max 255 characters
The email address uniquely identifies the customer and is used for communication. It must follow standard email syntax (local@domain.tld).
Field:email · Type: string · Required: YES · Max length: 255 · Format: RFC 5321 email
Valid values:✓ "alice@demo.com"  ·  "user+tag@example.co.uk"  ·  "first.last@company.org"
Invalid values:✗ "notanemail" → 400, field: email, message: "must be a valid email"  ·  "@nodomain" → 400  ·  "no@" → 400  ·  empty string → 400
Default:None — required field
RULE
U-03
role — Optional enum, allowed values: user · admin · moderator
A user's role controls what they are allowed to do in the system. If not provided, the system assigns "user" as the default. Any value outside the three allowed options must be rejected.
Field:role · Type: string (enum) · Required: NO · Allowed values: user, admin, moderator · Default: user
Valid values:✓ "user"  ·  "admin"  ·  "moderator"  ·  omitting the field (defaults to "user")
Invalid values:✗ "superadmin" → 400 VALIDATION_ERROR  ·  "ADMIN" (wrong case) → 400  ·  "" (empty string) → 400  ·  123 (integer) → 400
Default:"user" — if the field is omitted, the created record will have role: "user"
RULE
U-04
age — Optional integer, 0 to 150
Age is an optional demographic field. When provided it must be a whole number between 0 and 150 inclusive. Negative ages and decimal values are not valid.
Field:age · Type: integer · Required: NO · Min: 0 · Max: 150 · Default: null (not stored)
Valid values:✓ 0  ·  25  ·  150  ·  omitting the field entirely
Invalid values:✗ -1 → 400  ·  151 → 400  ·  25.5 (decimal) → 400  ·  "twenty" (string) → 400
RULE
U-05
Session capacity — maximum 500 objects per session
Each session has a combined object limit of 500 records across users, products, and orders. When the limit is reached, any create (POST) request must be rejected until objects are deleted.
Business rule:If the session already contains 500 objects, POST /api/v1/users must return 400 with error: LIMIT_EXCEEDED
Normal behaviour:✓ Requests succeed while total objects in session < 500
At limit:✗ POST with valid payload → 400 LIMIT_EXCEEDED · Delete existing objects to resume creating
6.2   Products Entity — Field Constraints & Business Rules
RULE
P-01
name — Required string, 1 to 100 characters
Every product must have a name so shoppers can identify it. The name appears in order confirmations and GraphQL responses.
Field:name · Type: string · Required: YES · Min length: 1 · Max length: 100
Valid values:✓ "Wireless Headphones"  ·  "USB-C Cable"  ·  "A" (single char is fine)
Invalid values:✗ omitted → 400, field: name  ·  "" (empty) → 400  ·  101-char string → 400
RULE
P-02
price — Required, positive decimal number, must be greater than zero
Price is the amount the store charges for the product. It must be a positive number. Zero and negative prices are not allowed — a product that costs nothing would be a data error, not a free item.
Field:price · Type: number (float) · Required: YES · Min: 0.01 (must be > 0) · No maximum enforced
Valid values:✓ 0.01  ·  9.99  ·  299.00  ·  10000.00
Invalid values:✗ 0 → 400, message: "price must be greater than 0"  ·  -5 → 400  ·  "free" (string) → 400  ·  omitted → 400
RULE
P-03
stock — Optional integer, 0 or more. Default is 0.
Stock represents how many units are available. A stock of zero means the product is listed but sold out. Stock can never be negative.
Field:stock · Type: integer · Required: NO · Min: 0 · No maximum · Default: 0
Valid values:✓ 0 (sold out, still valid)  ·  1  ·  500  ·  omitting the field (defaults to 0)
Invalid values:✗ -1 → 400, message: "stock must be ≥ 0"  ·  10.5 (decimal) → 400  ·  "ten" (string) → 400
Default:0 — a product created without specifying stock will show stock: 0
RULE
P-04
description — Optional string, max 500 characters. Defaults to empty string.
A free-text description of the product shown to shoppers. Optional, but helps buyers make decisions. Descriptions longer than 500 characters are rejected to keep responses compact.
Field:description · Type: string · Required: NO · Max length: 500 · Default: ""
Valid values:✓ "High quality noise-cancelling headphones"  ·  "" (explicitly empty, treated as no description)  ·  omitting the field
Invalid values:✗ 501-character string → 400
RULE
P-05
category — Optional string, max 50 characters. Defaults to "general".
Category groups products for filtering. Shoppers can filter by category using GET /api/v1/products?category=electronics. If no category is given, "general" is used.
Field:category · Type: string · Required: NO · Max length: 50 · Default: "general"
Valid values:✓ "electronics"  ·  "clothing"  ·  "books"  ·  "general"  ·  omitting the field
Invalid values:✗ 51-character string → 400
Default:"general" — if omitted, the product is placed in the general category
RULE
P-06
PATCH vs PUT — partial update vs full replacement
The store supports two update verbs with different behaviours. PATCH updates only the fields you send; all other fields keep their current values. PUT replaces the entire product; any field you omit reverts to its default. Use the wrong verb and data can be accidentally erased.
PATCH rule:PATCH /api/v1/products/{id} with { "price": 19.99 } — only price changes. name, stock, category are preserved exactly.
PUT rule:PUT /api/v1/products/{id} requires name and price. If stock is omitted it resets to 0. If category is omitted it resets to "general".
Both reject:✗ Unknown product ID → 404 NOT_FOUND  ·  Field validation failures → 400
6.3   Orders Entity — Field Constraints & Business Rules
RULE
O-01
quantity — Optional integer, must be greater than zero. Defaults to 1.
Quantity is how many units of the product are being ordered. An order for zero items makes no sense and must be rejected. Decimals (e.g. 1.5 items) are not allowed.
Field:quantity · Type: integer · Required: NO · Min: 1 (must be > 0) · No maximum · Default: 1
Valid values:✓ 1  ·  5  ·  100  ·  omitting the field (defaults to 1)
Invalid values:✗ 0 → 400 VALIDATION_ERROR, message: "quantity must be greater than 0"  ·  -1 → 400  ·  2.5 (decimal) → 400
Default:1 — if omitted, the order is for a single unit
RULE
O-02
status — Optional enum. Allowed values: pending · processing · completed · cancelled. Defaults to "pending".
Status tracks where an order is in its lifecycle. New orders start as "pending". The store manager moves them to "processing" when work begins and "completed" when dispatched. Either party may cancel at any stage.
Field:status · Type: string (enum) · Required: NO · Allowed: pending, processing, completed, cancelled · Default: pending
Valid values:✓ "pending"  ·  "processing"  ·  "completed"  ·  "cancelled"  ·  omitting the field
Invalid values:✗ "shipped" → 400 VALIDATION_ERROR  ·  "PENDING" (wrong case) → 400  ·  "" (empty string) → 400
Status flow:pending → processing → completed  ·  Any stage → cancelled  ·  Note: the API does not enforce transition order — any status can be set at any time
RULE
O-03
notes — Optional string, max 500 characters. No default.
Free text the customer or manager can attach to an order — delivery instructions, special requests, or internal notes. Optional on create and update.
Field:notes · Type: string · Required: NO · Max length: 500 · Default: null
Valid values:✓ "Please leave at the door"  ·  "" (empty string)  ·  omitting the field
Invalid values:✗ 501-character string → 400
RULE
O-04
expand parameter — embeds linked records inline on GET /api/v1/orders/{id}
The expand query parameter lets callers fetch the order and its related user and product in one API call, avoiding the need for three separate requests. Each expanded field is embedded as a nested object under its key.
Parameter:expand · Location: query string · Allowed values: "user", "product", "user,product" · Not required
With ?expand=user,product:✓ Response includes order.user (full user object) and order.product (full product object)
Deleted reference:✓ If the linked user or product was deleted, the expanded key returns null — the order itself is unaffected
Unknown order:✗ Unknown order ID → 404 NOT_FOUND regardless of expand param
RULE
O-05
Delete behaviour — deleting a user or product does not delete linked orders
When a user or product is deleted, all orders that reference it remain in the system. This means you can still retrieve the order; the expanded user or product field simply returns null. The order itself is not automatically cancelled.
Business rule:DELETE /api/v1/users/{id} → user is removed. Orders with user_id matching that user are NOT deleted. GET /api/v1/orders/{id}?expand=user returns order.user = null.
Expected behaviour:✓ Order list still includes orders from deleted users · Order detail still returns the order · Expanded fields for deleted references return null, not an error
6.4   Authentication — Constraints & Business Rules
RULE
A-01
JWT login — username and password are both required
To receive a JWT, the caller must send both username and password in the request body. Missing either field is a client error, not an auth failure.
Endpoint:POST /auth/token · Body: { "username": string, "password": string }
Valid credentials:✓ alice@demo.com / alice123 → 200, access_token returned  ·  bob@demo.com / bob123 → 200  ·  charlie@demo.com / charlie123 → 200
Invalid credentials:✗ Wrong password → 401 UNAUTHORIZED  ·  Unknown email → 401  ·  Missing username → 400 VALIDATION_ERROR  ·  Missing password → 400
Token lifetime:expires_in: 600 seconds (10 minutes). After expiry, calls with this token return 401.
RULE
A-02
API Key authentication — two keys with different roles
Callers can authenticate using a static API key sent in the x-api-key header instead of a JWT. The demo environment provides two fixed keys: one for standard users and one for admins.
Header:x-api-key: <key> — sent on any request
Valid API keys:✓ "demo-key-sandbox-2024" → role: user  ·  "admin-key-sandbox-2024" → role: admin
Invalid:✗ Any other key value → GET /auth/me returns { "authenticated": false } — the endpoint does not error, it simply reports unauthenticated
RULE
A-03
OAuth2 client credentials — grant_type must be exactly "client_credentials"
Machine-to-machine callers use the OAuth2 client credentials grant. The grant_type field must be the exact string "client_credentials". The demo provides one client with read and write scope.
Endpoint:POST /auth/oauth · Body: { "grant_type", "client_id", "client_secret", "scope" }
Valid request:✓ grant_type: "client_credentials" + client_id: "sandbox-client" + client_secret: "sandbox-secret" → 200 with access_token
Invalid requests:✗ grant_type: "password" → 400  ·  Wrong client_secret → 401 UNAUTHORIZED  ·  Missing grant_type → 400
6.5   Sample Test Data Set

Use the values below directly when creating test cases. Each row is a ready-to-use input. The "Expected Result" column tells you what the API should return so you know whether the test passed or failed.

Test ID Endpoint Request Body / Params Expected Result
TD-U-01 POST /api/v1/users { "name": "Alice Johnson", "email": "alice@demo.com" } 201 Created — user object with id, role: "user"
TD-U-02 POST /api/v1/users { "name": "Bob", "email": "bob@demo.com", "role": "admin", "age": 34 } 201 Created — user with role: "admin", age: 34
TD-U-03 POST /api/v1/users { "email": "alice@demo.com" } — missing name 400 VALIDATION_ERROR, details[0].field: "name"
TD-U-04 POST /api/v1/users { "name": "Eve", "email": "notanemail" } 400 VALIDATION_ERROR, details[0].field: "email"
TD-U-05 POST /api/v1/users { "name": "Eve", "email": "e@x.com", "role": "superadmin" } 400 VALIDATION_ERROR, details[0].field: "role"
TD-U-06 POST /api/v1/users { "name": "Eve", "email": "e@x.com", "age": -1 } 400 VALIDATION_ERROR, details[0].field: "age"
TD-P-01 POST /api/v1/products { "name": "Wireless Headphones", "price": 89.99, "stock": 50, "category": "electronics" } 201 Created — product with id, stock: 50
TD-P-02 POST /api/v1/products { "name": "USB-C Cable", "price": 9.99 } — stock and category omitted 201 Created — stock: 0, category: "general" (defaults applied)
TD-P-03 POST /api/v1/products { "name": "Free Item", "price": 0 } 400 VALIDATION_ERROR, details[0].field: "price"
TD-P-04 POST /api/v1/products { "name": "Bad Stock", "price": 5.00, "stock": -1 } 400 VALIDATION_ERROR, details[0].field: "stock"
TD-O-01 POST /api/v1/orders { "user_id": "<uuid>", "product_id": "<uuid>", "quantity": 2 } 201 Created — order with status: "pending", quantity: 2
TD-O-02 POST /api/v1/orders { } — empty body, all fields optional 201 Created — quantity: 1, status: "pending" (all defaults)
TD-O-03 POST /api/v1/orders { "quantity": 0 } 400 VALIDATION_ERROR, details[0].field: "quantity"
TD-O-04 POST /api/v1/orders { "status": "shipped" } 400 VALIDATION_ERROR, details[0].field: "status"
TD-O-05 PATCH /api/v1/orders/{id} { "status": "processing" } 200 OK — order.status: "processing", updated_at refreshed
TD-A-01 POST /auth/token { "username": "alice@demo.com", "password": "alice123" } 200 OK — access_token (JWT), token_type: "Bearer", expires_in: 600
TD-A-02 POST /auth/token { "username": "alice@demo.com", "password": "wrongpassword" } 401 UNAUTHORIZED
TD-A-03 POST /auth/token { "username": "alice@demo.com" } — missing password 400 VALIDATION_ERROR, details[0].field: "password"
TD-A-04 GET /api/v1/users/{id} Valid UUID from a previous create in this session 200 OK — full user object
TD-A-05 GET /api/v1/users/{id} 00000000-0000-0000-0000-000000000000 (unknown UUID) 404 NOT_FOUND
TD-A-06 GET /api/v1/users/{id} not-a-uuid (malformed ID) 400 VALIDATION_ERROR
What is this sandbox?

A multi-protocol API simulator built for demo environments. It exposes identical business data (Users, Products, Orders) across REST, GraphQL, and SOAP simultaneously — all sharing one session-scoped in-memory store. Use it to demonstrate API testing concepts, tooling integrations, and protocol comparisons without any persistent database setup.

💡 All data is session-scoped. Your session ID travels in the x-session-id response header. Data auto-expires after 30 minutes of inactivity. No data persists between sessions.
Base URL
http://localhost:3050
Protocols
ProtocolEndpoint
REST CRUD/api/v1/{entity}
GraphQL/graphql
SOAP 1.1/soap
Auth/auth/*
Flows/api/v1/flows/*
Session Management
MechanismValue
Response headerx-session-id
Cookiesandbox_session
Request headerx-session-id: <id>
TTL10 min (sliding)
Max objects/session500
Rate Limits
100 requests / 60 seconds per IP
Simulation Parameters

Append these query params to any REST endpoint to simulate real-world failure conditions:

ParameterTypeDescriptionExample
?delay=Ninteger (ms)Artificial latency. Max 10,000 ms.?delay=2000
?error=NHTTP statusForce a specific HTTP error code.?error=503
?random_fail=trueboolean~30% chance of 500 error. For retry testing.?random_fail=true