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.
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-2026
Data Lifetime
10 min inactivity
Rate Limit
100 req / 60 sec
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
Protocol
Endpoint
REST CRUD
/api/v1/{entity}
GraphQL
/graphql
SOAP 1.1
/soap
Auth
/auth/*
Flows
/api/v1/flows/*
Session Management
Mechanism
Value
Response header
x-session-id
Cookie
sandbox_session
Request header
x-session-id: <id>
TTL
10 min (sliding)
Max objects/session
500
Rate Limits
100 requests / 60 seconds per IP
Simulation Parameters
Append these query params to any REST endpoint to simulate real-world failure conditions:
Parameter
Type
Description
Example
?delay=N
integer (ms)
Artificial latency. Max 10,000 ms.
?delay=2000
?error=N
HTTP status
Force a specific HTTP error code.
?error=503
?random_fail=true
boolean
~30% chance of 500 error. For retry testing.
?random_fail=true
REST API — Users
Full CRUD for users, products, and orders. Data is session-scoped and auto-expires in 10 min.
Open sandbox — auth isn't required for CRUD. Use this section to learn and test it. 17 schemes available.
ℹ️ The data endpoints (REST · GraphQL · SOAP) accept requests with or without credentials. The tools below let you get a token to use across the sandbox, or test how any auth scheme behaves.
Mint or inspect a token, then apply it in the Auth Config box (sidebar) to authenticate your REST / GraphQL / SOAP calls.
These three plug into the sidebar Auth Config. Need Basic, Digest, HMAC, SigV4…? → Test an auth scheme.
➡ Apply a token in the Auth Config box (sidebar) to authenticate your data requests.
Pick a scheme, send it the way it expects, and get an instant pass/fail — from this UI or your own tool (Postman, RestAssured, Bruno, Karate, SoapUI, APIDog, Tricentis). Each scheme has a protected endpoint at /auth/protected/{id}. Full setup guide ↗
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 APIFunctional Requirements & Use Cases · v1.0 ·
1Introduction
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
2Functional 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)
"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
"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."
"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
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
3Use Cases
Detailed use cases showing pre-conditions, step-by-step flows, alternative paths, and expected outcomes for each key interaction.
UC-001Customer Places First OrderActor: 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
1Customer sends POST /api/v1/users with name and email. System returns a customer object including a unique id.
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.
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.
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-002Manager Processes and Fulfils an OrderActor: 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
1Manager sends GET /api/v1/orders?status=pending&sort=created_at&order=asc. System returns the oldest unprocessed orders first.
2Manager picks an order and sends GET /api/v1/orders/{id}?expand=user,product to see the full customer and product details.
3Manager sends PATCH /api/v1/orders/{id} with { "status": "processing" }. System updates the order and returns it with updated_at set.
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-003User Authenticates and Verifies IdentityActor: 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
1User sends POST /auth/token with { "username": "alice@demo.com", "password": "alice123" }. System returns access_token (JWT).
2User copies the access_token and sends GET /auth/me with header: Authorization: Bearer <token>. System returns identity: { sub, role }.
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-2026 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-004Developer Verifies Cross-Protocol Data ConsistencyActor: 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
1Tester sends POST /api/v1/users (REST). Notes the returned user id.
2Tester opens /graphql and runs: query { user(id: "<id>") { id name email } }. The same user appears — created by REST, read by GraphQL.
3Tester sends a SOAP CreateUser request (POST /soap, SOAPAction: CreateUser). The new user is returned.
4Tester runs GET /api/v1/users via REST. Both the REST-created and SOAP-created users appear in the list.
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-005Tester Validates Error HandlingActor: 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
1GET /api/v1/users?error=500 — server returns 500 SERVER_ERROR. Tool should show an error state, not crash.
2GET /api/v1/users?error=404 — server returns 404 NOT_FOUND. Tool should handle "not found" gracefully.
3GET /api/v1/users?delay=5000 — server waits 5 seconds. Tool should show a loading indicator and not time out prematurely.
4POST /api/v1/users with missing "name" field — server returns 400 VALIDATION_ERROR with details array. Tool should surface the field-level error message.
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" }
]
}
4API 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
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
5Error 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
6Test 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).
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")
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.
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)
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)
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.
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.
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.
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-2026" → role: user · "admin-key-sandbox-2026" → 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.
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.