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_KEY as a Bearer token when set.
  • Session storage: per-session JSON files stored under chats/<session-id>/chat.json and 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: YYYYMMDDHHMM in 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:

VariableValue
INTERNAL_API_KEYELEC3120 without quotation mark
Time14th 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:

Timehashed 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

HeaderRequired?TypeExplanation
RNGinteger between 1-418 inclusiveAllow you to force set the roll result. If missing or invalid, fallback to rolling between 1 to 418
Accepttext/html or application/jsonSpecifies the media type that is acceptable for the response.

Possible Responses

HTTP CodeCondition
200If Accept type is application/json, return the link to this docs webpage
308Permanent redirect to /docs i.e. this page
418There 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

HeaderRequired?TypeExplanation
RNGinteger between 1-418 inclusiveAllow you to force set the roll result. If missing or invalid, fallback to rolling between 1 to 418
Accepttext/html or application/jsonSpecifies the media type that is acceptable for the response.

Possible Responses

HTTP CodeCondition
200If Accept type is application/json, returns the JSON of POST example call and example response
308Permanent redirect to /docs#api-ask-post i.e. this next section
418There 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

HeaderRequired?Type / ExampleExplanation
AuthorizationBearer {HMAC}Hash API Key before using. Click here to learn the formula.
Content-Typeapplication/jsonRequest body must be JSON
session-id16 digits numberIf 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.
TopicsintegerTopic 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 CodeCondition
200OK — returns generated answer and session metadata.
400Bad Request — invalid session-id, invalid Topics, or malformed JSON body.
401Unauthorized (missing/invalid API key).
415Unsupported Media Type — non-JSON request body.
429Too Many Requests — rate limit exceeded.
500Server misconfigured or file write failure.
502Bad 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

HeaderRequired?TypeExplanation
RNGinteger between 1-418 inclusiveAllow you to force set the roll result. If missing or invalid, fallback to rolling between 1 to 418
Accepttext/html or application/jsonSpecifies the media type that is acceptable for the response.

Possible Responses

HTTP CodeCondition
200If Accept type is application/json, returns the JSON of POST example call and example response
308Permanent redirect to /docs#api-session-post i.e. the next section
418There 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

HeaderRequired?TypeExplanation
AuthorizationBearer {HMAC}Hash API Key before using. Click here to learn the formula.
session-id16 digits numberIf 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 CodeCondition
200OK — returns full chat history AND the chat's session id AND session metadata.
401Unauthorized (missing/invalid API key).
429Too Many Requests — rate limit exceeded.
500Server 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 CodeCondition
200If the server is able to return 5XX, it probably has the ability to return 200 already...
418There 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

HeaderRequired?TypeExplanation
RNGinteger between 1-418 inclusiveAllow you to force set the roll result. If missing or invalid, fallback to rolling between 1 to 418
Accepttext/html or application/jsonSpecifies the media type to be returned

Possible Responses

HTTP CodeCondition
200If Accept type is application/json, returns usage documentation in JSON format.
308Permanent redirect to /docs#api-paper-post i.e. the next section
418There 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

HeaderRequired?TypeExplanation
AuthorizationBearer {HMAC}Hash API Key before using. Click here to learn the formula.
session-id16 digits numberOnly accept active session IDs
TopicsintegerTopic selector id.

Body

Optional, provides additional prompts or specifications on the generated paper

Responses

HTTP CodeCondition
200OK — returns the generated paper.
400Bad Request — invalid session-id, or malformed JSON body.
401Unauthorized (missing/invalid API key).
413I didnt implement this. I hope I never have to...
415Unsupported Media Type — non-JSON request body.
429Too Many Requests — rate limit exceeded.
500Server misconfigured or file read/write failure.
502Bad 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 CodeCondition
200If 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/session currently 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.json using atomic writes; ensure file permissions allow the server user to write to that folder.