API — General conventions¶
This page describes the cross-cutting rules that apply to all PerfShop REST endpoints. It is the first page to read before tackling per-controller references.
Scope
The PerfShop backend exposes around 95 endpoints spread over 19 Spring controllers. All conventions listed here are applied uniformly, except where explicitly noted in the relevant controller page.
Base URL and versioning¶
| Environment | Base URL |
|---|---|
| Local development (Docker Compose) | http://localhost:9080 |
| Production (nginx on NAS) | https://perfshop-api.perfshop.io |
PerfShop does not use explicit versioning in the path: there is no /v1/ prefix. The entire API lives under /api/.... Changes follow a "rolling release" model — breaking changes are documented in the release notes.
Exchange format¶
- Requests: JSON (
Content-Type: application/json), exceptPOST /api/admin/products/{id}/imagewhich expectsmultipart/form-data. - Responses: JSON systematically (even for errors). Bodies are encoded in UTF-8.
- Dates: ISO 8601 format (
2026-04-08T10:30:00Zfor timestamps,1990-05-15for bare dates). - Monetary amounts:
BigDecimalserialized as a JSON number with 2 decimals (e.g.49.99). - Identifiers:
Long(64-bit integer) for DB entities, UUID (string) for tokens.
The three authentication mechanisms¶
PerfShop hosts three distinct authentication systems side by side, each dedicated to a user population.
flowchart LR
subgraph CLIENT["HTTP client"]
U1[End user<br/>browser]
U2[Instructor<br/>chaos-admin]
U3[Student<br/>chaos page]
end
subgraph API["Spring Boot backend"]
A1[HTTP session<br/>JSESSIONID cookie]
A2[X-Admin-Token<br/>in-memory UUID]
A3[X-Student-Token<br/>per-student UUID]
end
U1 -->|"POST /api/auth/login"| A1
U2 -->|"POST /api/admin/login"| A1
U2 -.->|"or direct header"| A2
U3 -->|"POST /pedagogique/join"| A3
A1 --> B1[CartController<br/>OrderController<br/>UserController]
A2 --> B2[AdminController<br/>ChaosController<br/>BusinessChaosController<br/>SecurityChaosController<br/>FunctionalChaosController<br/>ChaosScriptingController]
A3 --> B3[ChaosStudentController<br/>pedagogical endpoints]
HTTP session (end user)¶
- Established by
POST /api/auth/loginwith email + password. - Stored server-side in the Spring
HttpSession(key:LOGGED_IN_USER). - Automatically transmitted by the browser via the
JSESSIONIDcookie. - Duration: 30 minutes of inactivity by default (
server.servlet.session.timeout). - Destroyed by
POST /api/auth/logout(except in Business chaos level 3+ — see A11 grace period).
X-Admin-Token (instructor)¶
- Generated by
POST /api/admin/loginin addition to the session cookie. Returned in the body under theadminTokenkey. - Random UUID stored in memory in a
ConcurrentHashMapserver-side (VALID_ADMIN_TOKENSkey inAdminController). - Not persistent: reset on backend restart (intentional behavior — pedagogical value for memory chaos demos).
- Designed for cross-origin use (
curlscripts, automated tests, calls fromchaos-adminwhich is served by nginx on another port). - Transmitted in the HTTP header:
X-Admin-Token: 550e8400-e29b-41d4-a716-446655440000.
Dual authentication
Admin controllers accept either the session cookie OR the X-Admin-Token header. The AdminAuth.isAdmin(session, token) utility centralizes this check.
X-Student-Token (student)¶
- Generated by
POST /api/chaos/student/pedagogique/joinwhen a student joins a journey. - UUID unique per student, stored server-side in
PedagogiqueSessionService(database + optional memory cache). - Used by pedagogical endpoints (
/validate,/logique/questions,/finale/validate, etc.) to isolate each student's progression. - Transmitted in the HTTP header:
X-Student-Token: ab12cd34-.... - Also allows orders (
POST /api/orders) to receive the pedagogicalagentCodeif the student is on an active journey.
Standard error format¶
Any error response (4xx or 5xx) returns a JSON object containing at least the error key:
Some errors add contextual keys:
{
"error": "LICENSE_REQUIRED",
"chaos": "performance",
"requested": 3,
"maxFree": 1,
"message": "Level 3 not available without license. Maximum: 1",
"portalUrl": "https://perfshop.io"
}
{
"error": "Invalid data",
"fields": {
"phone": "Invalid phone format for FR — expected: 10 digits starting with 0 (e.g. 0612345678)",
"postalCode": "Invalid FR postal code '7500' — expected: 5 digits (e.g. 75001)"
}
}
Messages are always resolved server-side via I18nService from messages_XX.properties keys — never returned as raw keys.
HTTP codes used¶
PerfShop strictly follows standard HTTP semantics, with an important particularity for code 402.
| Code | Usage in PerfShop |
|---|---|
| 200 OK | Standard success of a GET request or an idempotent action |
| 201 Created | Resource creation (POST /api/orders, POST /api/admin/products, POST /api/admin/users) |
| 400 Bad Request | Missing or invalid parameter, incorrect JSON format |
| 401 Unauthorized | Missing or invalid authentication (no session, unknown token) |
| 402 Payment Required | Feature reserved for paid licenses (freemium). See freemium vs Pro |
| 403 Forbidden | Authenticated but not authorized (student mode locked, non-superadmin account, resource from another user) |
| 404 Not Found | Resource not found — also returned by the hidden admin portal when S10–S12 are inactive (concealment) |
| 409 Conflict | State conflict: incorrect pedagogical step, license already active, no active journey |
| 422 Unprocessable Entity | Business validation failed (email format, Luhn card, birth date, etc.) |
| 429 Too Many Requests | Limit reached: /pedagogique/join refuses beyond 500 simultaneous sessions |
| 500 Internal Server Error | Unhandled exception, DB deadlock, functional chaos error (NPE, StackOverflow, OOM) |
The special case of 402 Payment Required¶
PerfShop uses the 402 Payment Required code systematically to signal that a feature exceeds the current license limits. This is a deliberate API choice: it makes the freemium rule self-describing — an HTTP client can unambiguously detect that it is hitting a license wall, separately from a plain 403 Forbidden.
Endpoints that return 402 all carry the same LICENSE_REQUIRED body structure documented above.
Internationalization of error messages¶
PerfShop is multilingual, but does not negotiate language via HTTP Accept-Language. The language is fixed globally by an environment variable read at backend startup:
PERFSHOP_LANG=fr # default
PERFSHOP_LANG=en
PERFSHOP_LANG=es # (upcoming)
PERFSHOP_LANG=de # (upcoming)
PERFSHOP_LANG=it # (upcoming)
Practical consequences:
- A given deployment speaks a single language.
- To change language, the backend must be restarted with another value of
PERFSHOP_LANG. - Messages are resolved from
messages_fr.propertiesormessages_en.properties(or future files). Accept-Languageheaders sent by the client are ignored.
This choice is driven by the pedagogical use case: a training room works in a given language, and the consistency of logs and error messages across all workstations matters more than per-user localization.
See Internationalization for the mechanism details.
CORS headers¶
The backend accepts cross-origin calls from internal frontends via CorsConfig.java. Allowed origins in development typically include:
http://localhost:3010(React e-commerce frontend)http://localhost:3011(welcome home page)http://localhost:3012(chaos-admin)http://localhost:3013(monitoring)http://localhost:3014(scripts-ui, jmeter-ui)
The X-Admin-Token and X-Student-Token headers are explicitly exposed in the CORS configuration to allow reading/writing from browsers.
In production, origins are restricted to *.perfshop.io domains.
Pagination¶
Endpoints returning paginated lists use Spring Data Pageable. Standard format:
Request:
Response:
{
"content": [ /* ... page elements ... */ ],
"totalElements": 147,
"totalPages": 8,
"number": 0,
"size": 20,
"first": true,
"last": false
}
| Parameter | Default | Description |
|---|---|---|
page |
0 |
Page number (0-indexed) |
size |
20 |
Number of elements per page |
sort |
varies | Sort column (server-side whitelist to avoid PropertyReferenceException) |
Sort whitelist
For GET /api/products, only the columns id, name, price, category, createdAt, stock are accepted. An unknown sort parameter is silently replaced by id.
Specific response headers¶
| Header | Emitted by | Description |
|---|---|---|
X-Debug-Token |
POST /api/auth/login |
Only in Security chaos level ≥ 3 (fault S7). Weakly signed HMAC token. |
X-Session-Token |
Checkout endpoints | Scripting chaos level ≥ 1 — see Scripting chaos |
X-Request-ID |
Checkout endpoints | Scripting chaos level ≥ 1 |
X-Action-Token |
Checkout endpoints | Scripting chaos level ≥ 2 |
X-CSRF-Token |
Checkout endpoints | Scripting chaos level ≥ 3 |
X-Step-Token |
Checkout endpoints | Scripting chaos level ≥ 3 |
X-Signature |
Checkout endpoints | Scripting chaos level ≥ 3 (HMAC) |
These headers are documented in detail on the Scripting chaos page.
Endpoint naming conventions¶
PerfShop follows a tree organized by business function then by role:
/api/auth/* → end-user authentication
/api/products/* → product catalog (public)
/api/cart/* → cart (public or logged in)
/api/checkout/* → 4-step checkout journey
/api/orders/* → orders (logged in)
/api/countries → reference lists
/api/admin/* → administration (instructor)
/api/admin/chaos/* → chaos control (instructor)
/api/admin/portal/* → hidden portal (Security chaos Master level)
/api/chaos/public/* → chaos reads (monitoring, no auth)
/api/chaos/frontend/* → frontend chaos state relay
/api/chaos/student/* → student page (pedagogical journeys)
/api/license/* → license management
The /api/chaos/public/* and /api/chaos/frontend/state (on GET) endpoints are excluded from the ChaosInterceptor, which guarantees that they always respond — even when the rest of the backend is saturated by active chaos. This is the property that lets student monitoring keep displaying a consistent state under simulated failure conditions.
Minimal curl example¶
Scenario: user login, profile retrieval, adding a product to the cart.
# 1. Login — stores the session cookie in /tmp/cookies.txt
curl -c /tmp/cookies.txt \
-H "Content-Type: application/json" \
-d '{"email":"alice@example.com","password":"alice123"}' \
http://localhost:9080/api/auth/login
# 2. Profile (reuses the cookie)
curl -b /tmp/cookies.txt http://localhost:9080/api/auth/me
# 3. Add to cart
curl -b /tmp/cookies.txt \
-H "Content-Type: application/json" \
-d '{"productId":1,"quantity":2}' \
http://localhost:9080/api/cart/add
Admin scenario with X-Admin-Token (no cookie):
# 1. Admin login — retrieves adminToken from the response JSON
TOKEN=$(curl -s \
-H "Content-Type: application/json" \
-d '{"email":"admin@perfshop.fr","password":"admin"}' \
http://localhost:9080/api/admin/login | jq -r '.adminToken')
# 2. Drive CPU chaos to level 3
curl -H "X-Admin-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"intensity":80,"ratio":2}' \
http://localhost:9080/api/admin/chaos/cpu
# 3. Consolidated status
curl -H "X-Admin-Token: $TOKEN" \
http://localhost:9080/api/admin/chaos/status
Cross-cutting points of attention¶
Intentionally vulnerable endpoints
Some endpoints become deliberately vulnerable when Security chaos exceeds a certain level. These behaviors are pedagogical: they serve to illustrate OWASP faults in a controlled environment. The Security chaos page lists these faults exhaustively, and each endpoint page signals when it is affected.
The hidden admin portal
The AdminPortalController (/api/admin/portal/*) systematically returns 404 when Security chaos is strictly below level 4 (Master). This behavior is intentional — it lets the student discover the portal by fuzzing only when the instructor has enabled the Master level. See admin-portal.md.
Freemium 402 never blocks the backend
An endpoint that returns 402 does not interrupt chaos already running: it only prevents a student without a license from activating new ones beyond the allowed free level. The instructor with a license can keep driving chaos normally.