This API is used by Agora Quiz, a french, online platform where students submit questions about a course and challenge each other at answering them. The website has two account kinds: student and teachers. Teachers can create "groups" and share a code for their student to sign up or join that group.
This documentation presents our results at reverse-engineering the internal API used by the website. It is very incomplete and currently only focuses on the student's half of the website. Values marked with question marks indicate unknown behavior/values.
The API seems to have been written by french-speaking people, and while it is in english, some of the words use the french orthograph (eg. groupe
instead of group
).
You will need an account on the website to be able to log in.
POST https://agora-quiz.education/api/sign_in
-> {
username: "USERNAME",
password: "HASHED_PASSWORD"
}
<- User (full)
The server will respond with a JSON file, with a Set-Cookie
HTTP header containing your token (named connect.sid
).
The response contains an extended version of the User object, containing the hashed password.
The user's password must be hashed with a plain md5sum to obtain HASHED_PASSWORD
.
All of the following endpoints need to have the connect.sid
cookie transmitted. The other cookies are analytics-related and can be ignored.
To log back out, send a GET
request to /api/logout
:
GET https://agora-quiz.education/api/logout
<- {
"message": "disconnected"
}
GET https://agora-quiz.education/api/me
<- {
"user": User,
"counter": {
"playing_game": int,
"question_rejected": int,
"question_unvalid": ?
}
}
Returns your User object and a set of counters for the currenctly active games, likely used for the status bubbles on the "Jouer" and "Questions" buttons.
The current ID_GROUP
, required for most api calls, can be found in the returned User
object.
PUT https://agora-quiz.education/api/users/ID_USER
-> {
admin: bool,
avatar_json: string,
createdAt: Date,
current_groupe: Group,
current_groupe_id: int|null,
defeats: int,
equalities: int,
finished_goals: int,
good_answers_count: int,
groups: [Group],
id_user: int,
is_establishment: bool,
last_donation_alter: Date, // ?
lastLogin: Date,
new_mail: null|string,
new_password: null|string,
new_username: string,
password: string,
questions_added: int,
questions_sent: int,
score_user: int,
updatedAt: Date,
username: string,
victories: int,
}
GET https://agora-quiz.education/api/groupe/ID_GROUP/users/ID_USER/playing_games
<- {
"count": 0,
"rows": [Round]
}
Returns a list of the currently-played games (Round) by the given ID_USER
in the group ID_GROUP
.
ID_USER
can be any user that is member of the group.
GET https://agora-quiz.education/api/groupe/ID_GROUP/count_playable_themes
<- {
"count": int
}
Returns how many themes can be selected when playing rounds.
GET https://agora-quiz.education/api/groupe/ID_GROUP/get_users_short_list
<- {
"count": int, // The number of users in the group (?), admins may not count towards that number
"rows": [User]
}
Returns a list of the users in the group ID_GROUP
.
The users from that list have the fields id_user
, username
, avatar_json
, admin
, score_user
, questions_added
, users_groupe
GET https://agora-quiz.education/api/groupe/ID_GROUP/notifications
<- {
"count_new": int, // How many new notifications there are
"rows": [Notification] // (?)
}
GET https://agora-quiz.education/api/groupe/ID_GROUP/top_three
<- {
"count": int, // The number of users in the group (?), higher than that of get_users_short_list, so maybe admins count towards that number
"rows": [User] //
}
GET https://agora-quiz.education/api/groupe/ID_GROUP/three_random_playable_themes
<- [Theme]
GET https://agora-quiz.education/api/groupe/ID_GROUP/games/ID_GAME
<- Game {
id_game: string,
score_1: int,
score_2: int,
round: int,
is_playing: bool,
is_finished: bool,
is_cancelled: bool,
is_friendly: bool,
createdAt: Date,
updatedAt: Date,
groupe_id: int,
user_id_1: int,
user_id_2: int,
current_player: int,
user_1: User {
id_user: int,
username: string,
avatar_json: string,
admin: bool,
score_user: int,
questions_added: int,
users_groupes: [ShortUser],
groups: [Group],
},
user_2: User { /* Same as user_1 */ },
curr_player: {username: string},
}
To enter a game, the website uses a PUT request on that endpoint:
PUT https://agora-quiz.education/api/groupe/ID_GROUP/games/ID_GAME
-> {
groupe_id: int,
id_game: string,
is_playing: true
}
<- (same)
GET https://agora-quiz.education/api/ingame_questions/user/ID_USER/groupe/ID_GROUP/theme/THEME/order/ORDER_KEY/ORDER_BY/item_per_page/ITEMS/page/PAGE
-> {
ID_USER: int, // ID of the user
ID_GROUP: int, // ID of the group
THEME: int|"all", // ID of the theme or "all" for every theme
ORDER_KEY: "updatedAt"|"value_question"|"good_rep", // key of the question object -- potential injection?
ORDER_BY: "ASC"|"DESC", // ASCendant or DESCendant
ITEMS: int, // number of items per page
PAGE: int, // page number (begins at 1)
}
<- {
count: int,
rows: [Question (full)]
}
This endpoint can only be accessed when ID_USER
is the ID of the current user (for students).
POST https://agora-quiz.education/api/groupe/GROUP_ID/users_answers
-> {
answer_time: float,
groupe_id: int,
question_id: int,
response_id: int,
response_is_true: bool,
round_id: int,
theme_id: int,
user_id: int
}
<- {
"message": "users_answer has been succesfully added" // (sic) note the typo on succesfully
}
Sends an answer. Requires all fields to be set to correct values and requires a PUT
request with the updated scores/status to be sent to /api/groupe/GROUP_ID/games/GAME_ID
.
The User
object contains public information about each user:
{
"id_user": int, // The ID of that user, commonly referred to as ID_USER in this document
"username": string, // The username of that user
"admin": bool,
"is_establishment": bool, // (?)
"avatar_json": string, // Stringified JSON containing an Avatar object
"current_groupe_id": int, // (sic) The ID of the currently-selected group
"mail": string, // can be null
"lastLogin": Date,
"updatedAt": Date, // (?)
"createdAt": Date,
"score_user": int, // The current score of the user
"victories": int, // The number of rounds won by the user
"equalities": int, // (sic) The number of rounds drawn by the user
"defeats": int, // The number of rounds lost by the user
"questions_added": int, // The number of questions added by the user that got approved
"questions_sent": int, // The number of questions proposed by the user
"answer_count": int, // The number of questions answered by the user
"good_answer_count": int, // The number of correct answers given by the user
"finished_goals": int, // (?)
"last_donation_alter": string, // (?)
"current_groupe": Group, // The Group with id `current_groupe_id`
"groups": [Group], // The list of groups that the user belongs to; seems to contain an alternative set of proprieties
"users_groupes": [User], // (sic) (?) Sometimes set, only contains statistics
"users_groupe": User, // (sic) (?) Sometimes set, does not contain users_groupe. When set, contains the statistics of that user corresponding to the queried group *and a different ID* (make a new object?)
}
The Group
object contains public information about a group (aka a class or course, created by a teacher):
{
"id_groupe": int, // The ID of the group
"level": string, // The graduate/undergraduate level of the corresponding course, in text representation (french form only?)
"topic": string, // The topic of the corresponding course, can be set to "Autre" for "other"
"name_groupe": string, // (sic) the name of the group
"code_groupe": int, // (sic) 6-digit code used by students to join the group
"password_groupe": string, // (sic) the password, required to join the group
"description_groupe": ?, // (sic) can be null
"user_id": int, // ID of the user who created the group (?)
"color_groupe_1": int, // (sic) (?) can be null
"color_groupe_2": int, // (sic) (?) can be null
"timeToAnswer": int, // The maximum time that a student can take to answer a question, in seconds
"showGoodRep": bool, // (?)
"showOpponentAnswer": bool, // (?) whether or not users can see their opponent's answers at the end of a round, if false, only the correctness is displayed
"showLadder": int, // (?) The number of places to show on the leaderboard
"showLadderGoodAnswers": bool, // (?)
"showGroupStats": bool, // (sic) (?)
"restrictGroupNews": bool, // (sic) (?)
"helpMathFormula": bool, // (?)
"allowAddImage": bool, // (?)
"users_groupes": [ShortUser], // (sic) (?)
"admin_user": User, // (sic) (?) Sometimes not set, but when set it only contains `username`, `avatar_json`, `admin` and `users_groupes.score_user`
"users_groupe": ?, // (sic) (?) Sometimes set to a User instance
}
This object is a shorter representation of a user, used within Group.
{
"score_user": int, // The score of that user
"victories": int, // The number of rounds won by that user
"equalities": int, // (sic) The number of rounds drawn by the user
"defeats": int, // The number of rounds lost by the user
"questions_added": int, // The number of questions added by the user that got approved
"questions_sent": int, // The number of questions proposed by the user
"answer_count": int, // The number of questions answered by the user
"good_answer_count": int, // The number of correct answers given by the user
"finished_goals": int, // (?)
"lastActivity": Date, // (?)
"updatedAt": Date // (?)
The avatar is edited in a small editor and lets users personalize their profile picture. It is made up of three layers: the background head, the eyes and the mouth. This structure is usually manipulated in string format.
{
"form": {
"pattern": int, // The shape of the head
"colors": int // The background color of the head
},
"eye": {
"pattern": int, // The shape of the eyes
"colors": int // The color of the eyes
},
"mouth": {
"pattern": int, // The shape of the mouth
"colors": int // The color of the mouth
}
}
{
"id_theme": int,
"name_theme": string, // Name of the theme
"color_theme": int, // (?)
"groupe_theme": {
"is_playable": bool,
}
}
A Date can either be in its ISO string representation (yyyy-mm-ddThh:mm:ss.sssZ
).
The server can also return an object, for special values. Values encoutered so far are:
{val: "CURRENT_TIMESTAMP"}