All of Marbleland's functionality can be accessed programatically through an API which is documented here. A few notes on the format: If in JSON form, response and request body types are specified using the TypeScript type syntax. All query parameters not marked with an * are optional.
- Authentication
- API endpoints
- Defining playable games and leaderboard sources
- Data types
- MBPak Support
Some API calls require the user to be authenticated to perform them.
Authentication works by setting the Authorization header with a value of Bearer <your-token>, so for example Bearer JBajPPAt+w/2tshyPSWNskWs054Zl2tFuPIzmcq7+WI=. Alternatively, you can send a cookie called token with <your-token> as its value.
A token can be acquired through sign-in and sign-up.
Returns a list of all levels as an array of LevelInfo.
Get a .zip archive containing all the levels specified in the request.
Form parameters (Content-Type: application/x-www-form-urlencoded):
| Name | Type | Meaning |
|---|---|---|
| ids | string |
A comma-separated list of level IDs to include in the .zip. |
Query parameters:
| Name | Type | Meaning |
|---|---|---|
| assuming | 'none' | 'gold' | 'platinumquest' |
Defaults to 'platinumquest'. If present, specifies the set of default assets to exclude from the archive. For example, if set to 'gold', all MBG default assets won't be included with the .zip. |
| append-id-to-mis | boolean |
If present, each level's ID will be appended to the end of its corresponding .mis file. |
Get the .zip archive for a given level.
Query parameters:
| Name | Type | Meaning |
|---|---|---|
| assuming | 'none' | 'gold' | 'platinumquest' |
Defaults to 'platinumquest'. If present, specifies the set of default assets to exclude from the archive. For example, if set to 'gold', all MBG default assets won't be included with the .zip. |
| version | number |
Defaults to the current version. If present, the data of this specific version of the level is returned. Valid version numbers range from 1 to the level's current version number. |
| append-id-to-mis | boolean |
If present, each level's ID will be appended to the end of its corresponding .mis file. |
Get the .mbpak archive for a given level.
Query parameters:
| Name | Type | Meaning |
|---|---|---|
| assuming | 'none' | 'gold' | 'platinumquest' |
Defaults to 'platinumquest'. If present, specifies the set of default assets to exclude from the archive. For example, if set to 'gold', all MBG default assets won't be included with the .zip. |
| version | number |
Defaults to the current version. If present, the data of this specific version of the level is returned. Valid version numbers range from 1 to the level's current version number. |
| append-id-to-mis | boolean |
If present, each level's ID will be appended to the end of its corresponding .mis file. |
Get the image thumbnail for a given level.
Query parameters:
| Name | Type | Meaning |
|---|---|---|
| original | boolean |
If set, the original, uncompressed image thumbnail will be returned. Takes precedence over width and height. |
| width | number |
When used together with height, specifies the dimensions to resize the image to. The original image will be stretched to cover the new dimensions while maintaining its aspect ratio. |
| height | number |
See width. |
| version | number |
Defaults to the current version. If present, the data of this specific version of the level is returned. Valid version numbers range from 1 to the level's current version number. |
Get the (usually large) preview image for a given level. Note that not every level has one.
Query parameters:
| Name | Type | Meaning |
|---|---|---|
| original | boolean |
If set, the original, uncompressed preview image will be returned. Takes precedence over width and height. |
| width | number |
When used together with height, specifies the dimensions to resize the image to. The original image will be stretched to cover the new dimensions while maintaining its aspect ratio. |
| height | number |
See width. |
| version | number |
Defaults to the current version. If present, the data of this specific version of the level is returned. Valid version numbers range from 1 to the level's current version number. |
Returns a list of files (assets) a given level depends on as an array of string. Essentially returns the file paths of the .zip.
Query parameters:
| Name | Type | Meaning |
|---|---|---|
| assuming | 'none' | 'gold' | 'platinumquest' |
Defaults to 'platinumquest'. If present, specifies the set of default assets to exclude from the dependencies. For example, if set to 'gold', all MBG default assets won't be listed as a dependency. |
| append-id-to-mis | boolean |
If present, each level's ID will be appended to the end of its corresponding .mis file. |
| version | number |
Defaults to the current version. If present, the data of this specific version of the level is returned. Valid version numbers range from 1 to the level's current version number. |
Returns the metadata for a given level in the form of LevelInfo.
Query parameters:
| Name | Type | Meaning |
|---|---|---|
| version | number |
Defaults to the current version. If present, the data of this specific version of the level is returned. Valid version numbers range from 1 to the level's current version number. |
Returns the extended information for a given level in the form of ExtendedLevelInfo.
Returns the raw MissionData ScriptObject of the .mis file as a Record<string, string | string[]>.
Query parameters:
| Name | Type | Meaning |
|---|---|---|
| version | number |
Defaults to the current version. If present, the data of this specific version of the level is returned. Valid version numbers range from 1 to the level's current version number. |
Returns a list of packs a given level appears in as an array of PackInfo.
Returns the specified leaderboards for the given level in sorted order.
Response body:
{
scores: {
username: string,
score: number,
score_type: 'time' | 'score',
placement: number
}[]
}Requires authentication. Uploads a .zip archive containing a level and primes it for submission. Note that uploading through the API implies you have read and agreed to the Marbleland content guidelines.
Request body: The raw data of the .zip file with Content-Type: application/zip, or multipart form data with Content-Type: multipart/form-data containing the files to upload.
Response body:
{
// On error
status: 'error',
problems: string[] // A list of problems with the uploaded archive
} | {
// On successful upload
status: 'success',
uploadId: string, // The random ID of this upload. Needs to be remembered for submission.
missions: { // List of missions detected in the .zip.
misFilePath: string,
name: string
}[],
packs: PackInfo[], // The packs of the uploader. These are needed for displaying them during the upload process.
warnings: string[] // A list of warnings about the uploaded archive
}Requires authentication. For a given mission upload process, gets the image thumbnail for one of the uploaded missions.
Query parameters:
| Name | Type | Meaning |
|---|---|---|
| uploadId* | string |
The ID of the ongoing upload. |
| missionId* | number |
The index of the mission whose image thumbnail should be retrieved. |
Requires authentication. Submits a previously uploaded level, that is, adds it to the database and makes it accessible.
Request body (Content-Type: application/json):
{
uploadId: string,
remarks: string[], // Additional remarks for each uploaded level to display on that level's page
addToPacks: number[], // The IDs of the packs that all uploaded levels should be added to
newPack?: { // Info about a brand new pack that will be created and that the new levels will be added to immediately
name: string,
description: string
}
}Response body:
{
levelIds: number[], // The new IDs of the submitted levels
newPackId: number | null // The new ID of the created pack (if any)
}Requires authentication. Uploads a .zip archive containing a new version of the given level and primes it for submission. Only the level's uploader and moderators may do this, and the archive may only contain a single .mis file.
Request body: The raw data of the .zip file with Content-Type: application/zip, or multipart form data with Content-Type: multipart/form-data containing the files to upload.
Response body:
{
// On error
status: 'error',
problems: string[] // A list of problems with the uploaded archive
} | {
// On successful upload
status: 'success',
uploadId: string, // The random ID of this upload. Needs to be remembered for submission.
missions: { // List of missions detected in the .zip.
misFilePath: string,
name: string
}[],
warnings: string[] // A list of warnings about the uploaded archive
}Requires authentication. Submits a previously uploaded archive as the given level's new version, that is, replaces the level's content while keeping its identity (ID, statistics, comments and leaderboards) intact. The superseded version remains accessible through the version query parameter of the other endpoints.
Request body (Content-Type: application/json):
{
uploadId: string,
remarks: string[] // The first remark becomes the new version's changelog and is required to be a non-empty string
}Response body:
{
status: 'success',
version: number, // The version number of the newly created version
levelIds: number[] // Contains the ID of the updated level
}Requires authentication. Edit the metadata of a previously submitted level. Right now, the level's MissionInfo and remarks can be edited.
Request body (Content-Type: application/json):
{
missionInfo: Record<string, string> | null, // The new MissionInfo fields. Only certain fields are allowed to be changed, check shared/constants.ts.
remarks: string | null
}Response body: The extended information for the given level in the form of ExtendedLevelInfo after it was edited.
Requires authentication. Deletes a previously submitted level from the database.
Requires authentication. Adds a comment to given level. Returns the full list of comments (after submission) for a given level in the form of an array of CommentInfo.
Request body (Content-Type: application/json):
{
content: string // The content of the comment
}Requires authentication. Marks a level as loved.
Requires authentication. Unmarks a level as loved.
Requires authentication. Deletes a previously written comment. Returns the full list of comments (after deletion) for the comment's level in the form of an array of CommentInfo.
Registers a new account.
Request body (Content-Type: application/json):
{
email: string,
username: string,
password: string
}Response body:
{
// On error
status: 'error',
reason: string
} | {
// On successful account creation (when email verification is disabled)
status: 'success',
token: string,
signInInfo: SignInInfo
} | {
// When email verification is required
status: 'success',
requiresVerificationForEmail: string, // The email address that needs verification
message: string
}Sign in to an existing account.
Request body (Content-Type: application/json):
{
emailOrUsername: string,
password: string
}Response body:
{
// On error
status: 'error',
reason: string
} | {
// On successful sign-in
status: 'success',
token: string,
signInInfo: SignInInfo
} | {
// When email verification is required before sign-in
status: 'error',
reason: string,
requiresVerificationForEmail: string // The email address that needs verification
}Requires authentication. Signs out a previously signed-in account by invalidating its token.
Verifies an email address using a token received via email during account registration or sign-in. This endpoint handles both pending registrations and existing unverified accounts.
Query parameters:
| Name | Type | Meaning |
|---|---|---|
| token* | string |
The verification token sent via email. |
Response: Redirects to the user's profile page on successful verification, or returns an error response.
Requires authentication. Checks the validity of a token specified in the Authorization header. If it is valid, returns SignInInfo for the corresponding account.
Returns extended metadata about a given account in form of ExtendedProfileInfo.
Get the avatar image for a given account. Note that this will return a default image if the account hasn't set an avatar yet, and in that case the size query param won't do anything.
Query parameters:
| Name | Type | Meaning |
|---|---|---|
| size | number |
If set, resizes the avatar image to a square with side lengths of size. |
Requires authentication. Sets the avatar image for a given account.
Request body: The raw data of avatar image file with Content-Type: image/*.
Requires authentication. Sets the biography for a given account.
Request body: The new biography as a string with Content-Type: text/plain.
Requires authentication. Changes the password of the authenticated account.
Request body (Content-Type: application/json):
{
currentPassword: string,
newPassword: string
}Requests a password reset for an account by email address. A password reset link will be sent to the email if an account with that email exists.
Request body (Content-Type: application/json):
{
email: string
}Response body:
{
// On error
status: 'error',
reason: string
} | {
// On success (always returns success for security, even if email doesn't exist)
status: 'success',
message: string // "If an account with this email exists, a password reset link has been sent."
}Resets the password for an account using a valid reset token received via email. Upon successful reset, the user will be automatically signed in.
Request body (Content-Type: application/json):
{
token: string, // The reset token from the email
password: string // The new password
}Response body:
{
// On error
status: 'error',
reason: string
} | {
// On successful password reset
status: 'success',
token: string,
signInInfo: SignInInfo
}Requires authentication. Acknowledges the upload content guidelines for the authenticated account.
Requires authentication and moderator privileges. Suspends the specified account. This will:
- Mark the account as suspended
- Delete all levels, packs, and comments created by the user
- Prevent the user from performing any actions while suspended
Request body (Content-Type: application/json):
{
reason: string // Required. The reason for suspension (max 500 characters). Will be displayed publicly on the user's profile.
}Response body:
{
success: boolean
}Requires authentication and moderator privileges. Removes the suspension from the specified account. Note that this does not restore any previously deleted content.
Response body:
{
success: boolean
}Requires authentication and moderator privileges. Grants or revokes curator status for the specified account. When revoking status, any existing curator votes by this user are automatically removed.
Request body (Content-Type: application/json):
{
isCurator: boolean // true to grant, false to revoke
}Response body:
{
success: boolean,
isCurator: boolean // The new status
}Returns a list of all packs as an array of PackInfo.
Returns extended metadata about a pack in the form of ExtendedPackInfo
Get the .zip archive for a given pack, containing all levels the pack contains.
Query parameters:
| Name | Type | Meaning |
|---|---|---|
| assuming | 'none' | 'gold' | 'platinumquest' |
Defaults to 'platinumquest'. If present, specifies the set of default assets to exclude from the archive. For example, if set to 'gold', all MBG default assets won't be included with the .zip. |
Get the image thumbnail of a given pack.
Query parameters:
| Name | Type | Meaning |
|---|---|---|
| original | boolean |
If set, the original, uncompressed image thumbnail will be returned. Takes precedence over width and height. |
| width | number |
When used together with height, specifies the dimensions to resize the image to. The original image will be stretched to cover the new dimensions while maintaining its aspect ratio. |
| height | number |
See width. |
Requires authentication. Creates a new, empty level pack.
Request body (Content-Type: application/json):
{
name: string,
description: string
}Response body:
{
packId: number // The ID of the just created pack
}Requires authentication. Sets the list of levels included in a given pack.
Request body (Content-Type: application/json):
number[] // An array of level IDs to include in the packRequires authentication. Edits the metadata of a given pack.
Request body (Content-Type: application/json):
{
name: string,
description: string
}Requires authentication. Edits a given pack from the database.
Requires authentication. Marks a pack as loved.
Requires authentication. Unmarks a pack as loved.
Requires authentication and curator privileges. Casts a curator vote on a level. If the resulting score is below threshold, it is hidden from normal view.
Request body (Content-Type: application/json):
{
vote: boolean | null // true = upvote, false = downvote, null = retract vote
}Response body:
{
success: boolean,
}Returns the necessary data for the Home page in the form of HomeInfo.
Marbleland supports the ability to launch the game directly into the level of your choice by the use of the Play button. It also provides a view of the leaderboards available for the level.
They are defined in the server/data/config.json file having the structure.
{
games: GameDefinition[],
leaderboardSources: LeaderboardDefinition[]
}The queryUrl of the leaderboard definition contains the endpoint of the leaderboards which should be queried by the server to fetch the scores.
The response of the server should be in the format of:
{
scores: {
username: string,
score: number,
score_type: 'time' | 'score',
placement: number
}[]
}Example configuration values:
"games": [
{
"id": "webport",
"name": "Webport",
"playUrl": "https://marbleblast.vaniverse.io/?play={id}",
"datablockCompatibility": "mbw"
}
],
"leaderboardSources": [
{
"id": "marbleblast",
"name": "marbleblast.com",
"queryUrl": "https://marbleblast.com/pq/leader/api/Score/GetMarblelandScoresApi.php?missionId={id}",
"datablockCompatibility": "pq"
}
]The following describes a set of object data types used in the API.
Contains the content-derived metadata of a level.
{
baseName: string,
gameType: 'single' | 'multi',
modification: 'gold' | 'platinum' | 'fubar' | 'ultra' | 'platinumquest',
name: string,
artist: string,
desc: string,
gameMode: string,
qualifyingTime: number,
goldTime: number,
platinumTime: number,
ultimateTime: number,
awesomeTime: number,
qualifyingScore: number,
goldScore: number,
platinumScore: number,
ultimateScore: number,
awesomeScore: number,
gems: number,
hasEasterEgg: boolean,
hasCustomCode: boolean,
datablockCompatibility: 'mbg' | 'mbw' | 'pq', // Which variant of Marble Blast this level's datablocks are compatible with
}Contains metadata about a level: the content of its current version, plus its identity and social data.
LevelContentInfo & {
id: number,
addedAt: number,
editedAt: number, // Timestamp of when the level was last edited, null if it hasn't been edited.
downloads: number,
lovedCount: number,
curationScore: number, // Cumulative curator score
currentVersion: number, // The version number of the level's current (latest) version
}Contains metadata about a level, as well as additional data to display on the Level page.
LevelInfo & {
addedBy: ProfileInfo,
remarks: string,
packs: PackInfo[],
comments: CommentInfo[],
downloads: number,
missesDependencies: boolean,
lovedByYou: boolean, // Indicates if the logged-in user has loved the level
hasPrevImage: boolean,
missionInfo: Record<string, string>, // All the properties of the .mis file's MissionInfo element
dependencies: string[], // The list of files (assets) the given level depends on
playInfo: GameDefinition[], // Contains the definitions of the games the level can be played on
leaderboardInfo: ReducedLeaderboardDefinition[], // Contains the definitions of the leaderboards available for the level
// Visible to curators only:
curatorVotes: CuratorVoteInfo[], // All the votes cast by curators
yourVote: boolean, // Your curator vote; true for upvote, false for downvote
previousVersions: LevelContentInfo[], // The content of every superseded version, oldest to newest; previousVersions[n] contains version n+1 (versions are 1-indexed). The current version's content is the level info itself. Empty if the level was never updated.
versionMetadata: VersionMetadata[], // Same length as previousVersions but offset by one semantically: versionMetadata[n] describes version n+2, so the last entry describes the current version. The first version has no metadata; it was added at addedAt and has no changelog.
}Contains the metadata of a single level version (as opposed to its content).
{
addedAt: number, // When this version was added
changelog: string // The changes made in this version compared to the previous one
}Contains a curator vote (up or down) by a profile
{
profile: ProfileInfo,
vote: boolean
}Contains the definition of a game the level can be played on
{
id: string,
name: string,
datablockCompatibilities: ['mbg' | 'mbw' | 'pq'][], // The list of datablock compatibilities this game can play
playUrl: string, // The direct link to play the level in the defined game
customCodeAllowed: boolean, // Whether this game allows playing levels that utilise custom code.
}Contains the definition of a leaderboard source for levels.
{
id: string,
name: string,
datablockCompatibility: ['mbg' | 'mbw' | 'pq'][], // The list of datablock compatibilities this game can play
queryUrl: string, // The leaderboard endpoint that can be used to query the leaderboard for the level
customCodeAllowed: boolean, // Whether this game allows playing levels that utilise custom code.
}Describes an available leaderboard for the frontend. Reduced in order not to leak the query URL, which is only known by the server.
Pick<LeaderboardDefinition, 'id' | 'name'>Contains metadata about a pack.
{
id: number,
name: string,
createdBy: ProfileInfo,
createdAt: number,
levelIds: number[],
downloads: number,
lovedCount: number
}Contains metadata about a pack.
{
id: number,
name: string,
description: string,
createdBy: ProfileInfo,
createdAt: number,
levels: LevelInfo[],
downloads: number,
lovedCount: number,
lovedByYou: boolean // Indicates if the logged-in user has loved the pack
}Contains data about a comment.
{
id: number,
author: ProfileInfo,
time: number,
content: string
}Contains metadata about a profile.
{
id: number,
username: string,
hasAvatar: boolean,
isModerator: boolean,
isCurator: boolean,
isSuspended?: boolean,
suspensionReason?: string
}Contains metadata about a profile, as well as additional data to display on the Profile page.
ProfileInfo & {
bio: string,
uploadedLevels: LevelInfo[], // Newest levels first
favoriteLevels: LevelInfo[], // Newest levels first
createdPacks: PackInfo[],
curatorVotesCast?: number // Total curator votes submitted by this user
}Contains data that is remembered by the client upon login.
{
profile: ProfileInfo,
packs: { // A list of all packs belonging to that user
id: number,
name: string,
levelIds: number[]
}[],
curatorVotes?: Record<number, boolean> // For curators, all the votes they have cast, keyed by level ID
}Describes the data displayed on the Home page.
{
latestLevels: LevelInfo[]
}Marbleland also supports retrieving levels as MBPak archives that can be directly installed by PQ without having to extract it. To set it up, you require a key.txt containing the RSA and AES-256 encryption keys at the server/data directory.
Format of key.txt: (replace RSAKEYRSAKEY... with your RSA key)
# -----BEGIN RSA PRIVATE KEY-----
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# -----END RSA PRIVATE KEY-----
# -----BEGIN RSA PUBLIC KEY-----
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# RSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEYRSAKEY
# -----END RSA PUBLIC KEY-----
# -----BEGIN AES KEY-----
# < SHA256 hash of any plaintext password to be used as aes key, remove the brackets >
# -----END AES KEY-----