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/schema — 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/schema — 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 10 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
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 10 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