API Documentation — LearningPacer
This page documents the server HTTP API specification that will be implemented inAPI_Provider.py. All endpoints are described here on a single page (left navigation will be re-purposed to link back to other pages soon).
Common Runtime Defaults
- INTERNAL_API_KEY: read from environment; many handlers require
INTERNAL_API_KEYas a Bearer token when set. - Session storage: per-session JSON files stored under
chats/<session-id>/chat.jsonand written automically. - AZURE_AI_MODEL: configured via .env. If not set, AI calls will return 502.
Authentication
Authentication is driven by the presence of the INTERNAL_API_KEY environment variable.
- To protect the site from unwelcomed access around the globe, many POST endpoints require the header
Authorization: Bearer <INTERNAL_API_KEY>. - Some GET endpoints (help/usage pages, health, rickroll) remain public.
- Since 7th April, 2026, the dev team used SHA256 to store the hashed
INTERNAL_API_KEY. If you have the plain text, please hash it yourself before substituting it. - We realized that SHA256 alone was not enough. Starting from 14 April 2026, we use HMAC encoding. Methodology will be provided soon.
- If you are seeing this from fyp.schoolisboring.site/docs owned by Tiramisu1th, you can DM me via discord (tiramisu_1th) for an unhashed INTERNAL_API_KEY
HMAC Hashing
In this project, we adopt this formula for HMAC Hashing:
- the basic
INTERNAL_API_KEY: If you are seeing this from fyp.schoolisboring.site/docs owned by Tiramisu1th, you can DM me via discord (tiramisu_1th) for an unhashed INTERNAL_API_KEY - PAYLOAD: the entire request body JSON. If
request.get_json()says it is malformed, it will be replaced by empty string - TIMESTAMP:
YYYYMMDDHHMMin UTC+8 (Hong Kong Time), totalling 12 digits
After that, simply concatenate in the order of Content -> Timestamp -> Key, and make sure to SHA256
HMAC_unhashed: str = json.dumps((request.get_json(silent=True) or ""), separators=(',', ':'), ensure_ascii=False, sort_keys=True) + str(timestamp) + INTERNAL_API_KEY
HMAC: str = SHA256(HMAC_unhashed)
Authorization: str = "Bearer " + HMAC- For timestamp, we allow leniency of plus or minus 1 minute i.e. we will check timestamp with current minute, previous minute and next minute
- If you are seeing this from fyp.schoolisboring.site/docs owned by Tiramisu1th, you can DM me via discord (tiramisu_1th) for an unhashed INTERNAL_API_KEY
Example of HMAC:
| Variable | Value |
|---|---|
| INTERNAL_API_KEY | ELEC3120 without quotation mark |
| Time | 14th May, 2025 7:19:19 p.m. |
| Payload | {"question":"What is TCP?","odour":"sudden fiercing smell"} |
Therefore, the HMAC would be calculated as follows:
HMAC_unhashed: str = "{"question":"What is TCP?","odour":"sudden fiercing smell"}" + "202505141919" + "ELEC3120" Resulting in: {"question":"What is TCP?","odour":"sudden fiercing smell"}202505141919ELEC3120
Note that even if the body contains irrelevant key-value pairs, it will still be included in the HMAC calculation.
If you want to replicate the example:
SHA256 conversion website: https://emn178.github.io/online-tools/sha256.html
Expected hashed key: a8faf1fa5219912f4461d52bc65d766096354f8209331dc59cb909be6ac98d94
If the system received the request 1 minute later at 19:20, the system checks these keys:
| Time | hashed key |
|---|---|
| 14th May, 2025 7:19 p.m. | a8faf1fa5219912f4461d52bc65d766096354f8209331dc59cb909be6ac98d94 |
| 14th May, 2025 7:20 p.m. | dada55e5f53ec519f91cdb4962e5e045bff1f05655128c0f4a626d8db74e9fde |
| 14th May, 2025 7:21 p.m. | 06685f93bf2306845a53afae9dfaf2cb357766a49adb64d926b7f44b068d9dcc |
With this implementation, I am finally safe from Eve stealing my AzureAPI Key to ask a different question!
Rate Limiting
The server uses an in-memory per-IP rate limiter. Defaults: RATE_LIMIT_REQUESTS=3, RATE_LIMIT_WINDOW=60 seconds. Exceeding the limit returns 429 Too Many Requests.
Endpoints
/api
GET
Redirects you to this documentation page.
Headers
| Header | Required? | Type | Explanation |
|---|---|---|---|
| RNG | ❌ | integer between 1-418 inclusive | Allow you to force set the roll result. If missing or invalid, fallback to rolling between 1 to 418 |
| Accept | ❌ | text/html or application/json | Specifies the media type that is acceptable for the response. |
Possible Responses
| HTTP Code | Condition |
|---|---|
| 200 | If Accept type is application/json, return the link to this docs webpage |
| 308 | Permanent redirect to /docs i.e. this page |
| 418 | There is a 1/418 chance that you encounter this prankster teapot code. Just reload the page |
Example Call
curl -X GET "{BASE_URL}/api"P.S. Our poop mountain code makes localhost mode unable to execute this behaviour properly >.<
/api/ask
GET
Headers
| Header | Required? | Type | Explanation |
|---|---|---|---|
| RNG | ❌ | integer between 1-418 inclusive | Allow you to force set the roll result. If missing or invalid, fallback to rolling between 1 to 418 |
| Accept | ❌ | text/html or application/json | Specifies the media type that is acceptable for the response. |
Possible Responses
| HTTP Code | Condition |
|---|---|
| 200 | If Accept type is application/json, returns the JSON of POST example call and example response |
| 308 | Permanent redirect to /docs#api-ask-post i.e. this next section |
| 418 | There is a 1/418 chance that you encounter this prankster teapot code. Just reload the page |
Example Call
curl -X GET "{BASE_URL}/api/ask"P.S. Our poop mountain code makes localhost mode unable to execute this behaviour properly >.<
POST
Headers
| Header | Required? | Type / Example | Explanation |
|---|---|---|---|
| Authorization | ✅ | Bearer {HMAC} | Hash API Key before using. Click here to learn the formula. |
| Content-Type | ✅ | application/json | Request body must be JSON |
| session-id | ❌ | 16 digits number | If omitted the server generates a new session ID and opens a new chat. If the provided ID already exists the server continues that session. If a unique (non-existing) 16-digit ID is provided the server opens a new chat with that ID. |
| Topics | ❌ | integer | Topic selector id. |
Note 1: IDs shorter than 16 digits are left-padded with leading zeros to reach 16 digits.
Note 2: 0000000000000000 is reserved and will be rejected.
Note 3: Content-Type is Train-Case and session-id is kebab-case, pls be aware
Body
JSON object with a question string field. Example:
{
"question": "What is TCP?"
}Possible Responses
| HTTP Code | Condition |
|---|---|
| 200 | OK — returns generated answer and session metadata. |
| 400 | Bad Request — invalid session-id, invalid Topics, or malformed JSON body. |
| 401 | Unauthorized (missing/invalid API key). |
| 415 | Unsupported Media Type — non-JSON request body. |
| 429 | Too Many Requests — rate limit exceeded. |
| 500 | Server misconfigured or file write failure. |
| 502 | Bad Gateway — AI model not configured or AI request failed. |
Example Call
curl -X POST "{BASE_URL}/api/ask"
-H "Content-Type: application/json"
-H "Authorization: Bearer {HMAC}"
-H "session-id: 3120"
-d '{
"question": "What is TCP?"
}'/api/session
Chat history retrieval endpoint. The GET method returns usage/documentation; the POST method is working in progress.
GET
Headers
| Header | Required? | Type | Explanation |
|---|---|---|---|
| RNG | ❌ | integer between 1-418 inclusive | Allow you to force set the roll result. If missing or invalid, fallback to rolling between 1 to 418 |
| Accept | ❌ | text/html or application/json | Specifies the media type that is acceptable for the response. |
Possible Responses
| HTTP Code | Condition |
|---|---|
| 200 | If Accept type is application/json, returns the JSON of POST example call and example response |
| 308 | Permanent redirect to /docs#api-session-post i.e. the next section |
| 418 | There is a 1/418 chance that you encounter this prankster teapot code. Just reload the page |
Example Call
curl -X GET "{BASE_URL}/api/session" -H "Accept: text/html"P.S. Our poop mountain code makes localhost mode unable to execute this behaviour properly >.<
POST
Headers
| Header | Required? | Type | Explanation |
|---|---|---|---|
| Authorization | ✅ | Bearer {HMAC} | Hash API Key before using. Click here to learn the formula. |
| session-id | ❌ | 16 digits number | If omitted the server generates a new session ID and initializes a new chat. If the provided ID already exists the server returns that session. If a unique (non-existing) 16-digit ID is provided the server initializes a new chat with that ID. |
Body
Nobody
Possible Responses
| HTTP Code | Condition |
|---|---|
| 200 | OK — returns full chat history AND the chat's session id AND session metadata. |
| 401 | Unauthorized (missing/invalid API key). |
| 429 | Too Many Requests — rate limit exceeded. |
| 500 | Server misconfigured or file read/write failure. |
Example Call
curl -X POST "{BASE_URL}/api/session"
-H "Authorization: Bearer {HMAC}"
-H "session-id: 0213000000000000"
-d '{"This line is just trying to":"Make HMAC more complicated","This is actually":"Completely redundant"}'/api/health
GET
Headers
bro are you serious?
Body
Stop it, get some help
Possible Responses
| HTTP Code | Condition |
|---|---|
| 200 | If the server is able to return 5XX, it probably has the ability to return 200 already... |
| 418 | There is a 1/418 chance that you encounter this prankster teapot code. Just reload / re-ping the page |
Example Call
curl -X GET "{BASE_URL}/api/health"/api/paper
Paper-generation endpoint. The GET method returns usage/documentation; the POST method is currently a stub (returns 501).
GET
Headers
| Header | Required? | Type | Explanation |
|---|---|---|---|
| RNG | ❌ | integer between 1-418 inclusive | Allow you to force set the roll result. If missing or invalid, fallback to rolling between 1 to 418 |
| Accept | ❌ | text/html or application/json | Specifies the media type to be returned |
Possible Responses
| HTTP Code | Condition |
|---|---|
| 200 | If Accept type is application/json, returns usage documentation in JSON format. |
| 308 | Permanent redirect to /docs#api-paper-post i.e. the next section |
| 418 | There is a 1/418 chance that you encounter this prankster teapot code. Just reload the page |
Example Call
curl -X GET "{BASE_URL}/api/paper" -H "Accept: text/html"POST
Headers
| Header | Required? | Type | Explanation |
|---|---|---|---|
| Authorization | ✅ | Bearer {HMAC} | Hash API Key before using. Click here to learn the formula. |
| session-id | ✅ | 16 digits number | Only accept active session IDs |
| Topics | ❌ | integer | Topic selector id. |
Body
Optional, provides additional prompts or specifications on the generated paper
Responses
| HTTP Code | Condition |
|---|---|
| 200 | OK — returns the generated paper. |
| 400 | Bad Request — invalid session-id, or malformed JSON body. |
| 401 | Unauthorized (missing/invalid API key). |
| 413 | I didnt implement this. I hope I never have to... |
| 415 | Unsupported Media Type — non-JSON request body. |
| 429 | Too Many Requests — rate limit exceeded. |
| 500 | Server misconfigured or file read/write failure. |
| 502 | Bad Gateway — AI model not configured or AI request failed. |
Example Call
curl -X POST "{BASE_URL}/api/paper"
-H "Authorization: Bearer {HMAC}"
-H "session-id: 1991"
-d '{
"question": "Include an essay question specifically about DHCP"
}'/api/rickroll
GET
Headers
Never gonna give you up, never gonna let you down
Body
Never gonna run around and desert you
Possible Responses
| HTTP Code | Condition |
|---|---|
| 200 | If the server is able to return 5XX, go find Rick Astley dont find me... |
Example Call
curl -X GET "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
Other UI endpoints
The server includes a minimal web UI for the AI past paper generator at /menu (GET) and a runner at /menu/run (POST). These return HTML pages and are intended for manual browser use.
Notes & Recommendations
- Log leakage:
/api/sessioncurrently logs an attempted Authorization header value when it is wrong — this can leak secrets to logs and should be redacted. - Auth consistency: consider unifying auth semantics (either always require INTERNAL_API_KEY or document which endpoints are intentionally public).
- Persistence: session messages are saved under
chats/<session-id>/chat.jsonusing atomic writes; ensure file permissions allow the server user to write to that folder.