A save file is a named set of entries that belongs to one or more players, its owners. Whoever creates a file is its first owner. Every owner has full control: they change its entries, make it public or private, add owners and remove them. A game that never adds anybody simply has files with one owner each.
There is no deleting a file. Owners leave it, and when the last owner has left, the file and
everything it stored are gone. Anything finer - only the creator may change it, say - is for the
game to enforce; every file says who made it (createdBy).
| Private | Public | |
|---|---|---|
| Read | its owners only | anyone, no sign-in needed |
| Write | its owners only | its owners only |
A private file that is not yours answers 404, exactly like a file that does not exist.
A file is addressed by its id, which you get when you create it and in every list. Its name
(slot1, party: letters, digits, ., - and _, up to 128 characters) is a label: two files
may share one.
The project can cap how many files one player may have, on its Save files page. Every file counts
for every one of its owners. Being added to a file is never refused for it, so it can take a player
over the cap; they then cannot create a new file until they are under it again (409).
A file holds entries, each under a key (anything printable except / and \, up to 255
characters) and each one of four types:
| Type | Value |
|---|---|
int |
A whole number, 64-bit. |
string |
Any text. |
json |
Any JSON value, nested at most 64 deep. |
file |
An uploaded file. Executables are refused (by name and by content), up to 64 MB. |
Strings and JSON over 2 MB, and every uploaded file, are kept in file storage rather than the database; nothing about reading or writing them changes. Everything kept there is encrypted, and only ever comes back through MGDS, which checks who is asking.
Finding files
GET /me/saves every file you own
GET /me/saves?name=slot1 only those called slot1
GET /players/{playerId}/saves that player's files: all of them if it is you, the public ones otherwise
Each lists id, name, public, createdBy and updatedAt.
Creating one
POST /me/saves
{ "name": "slot1", "public": false,
"entries": { "level": { "type": "int", "value": 1 } } }
name is required, the rest optional. 201 with the whole file, including its id.
Reading one
GET /saves/{id}
Authorization: Bearer mgds_... (only needed for a private file)
The whole file, as one object. Entries are under entries by their key, so a game reads
save.entries.level or save.entries.inventory[3].count directly; types says what each one is.
{
"id": "0b8c...", "name": "slot1", "public": false,
"createdBy": "6f0c1c9e-...", "owners": [ "6f0c1c9e-..." ],
"createdAt": "...", "updatedAt": "...",
"entries": {
"level": 12,
"name": "Maffin",
"inventory": [ { "item": "sword", "count": 1 } ],
"avatar": { "url": "https://www.dasmaffin.com/api/mgds/v1/projects/.../saves/0b8c.../files/avatar",
"name": "avatar.png", "bytes": 48213, "mime": "image/png" }
},
"types": { "level": "int", "name": "string", "inventory": "json", "avatar": "file" }
}
A file entry's url downloads it, under the same rule as the file. It always arrives as a download.
Writing
Owners only, with their session.
Several things at once - name, visibility, entries, removals. All of it is saved, or none of it:
PUT /saves/{id}
{
"name": "slot-one",
"public": true,
"entries": {
"level": { "type": "int", "value": 12 },
"inventory": { "type": "json", "value": [ { "item": "sword", "count": 1 } ] }
},
"remove": [ "oldKey" ]
}
One entry:
PUT /saves/{id}/entries/{key}
{ "type": "string", "value": "Maffin" }
Part of a JSON entry, by a JSON Pointer path, without sending the rest:
PATCH /saves/{id}/entries/{key}
{ "path": "/inventory/3/count", "value": 5 }
An object member that is not there yet is added; an array index has to exist, or be - to append.
{ "path": "/inventory/0", "remove": true } removes it. In a key, ~ is written ~0 and / is ~1.
A file entry, as multipart/form-data with the file in a field called file:
PUT /saves/{id}/files/{key}
415 for an executable, 413 over 64 MB.
Removing an entry:
DELETE /saves/{id}/entries/{key}
Owners
GET /saves/{id}/owners who owns it
PUT /saves/{id}/owners/{playerId} add a player of this project
DELETE /saves/{id}/owners/{playerId} remove one - your own id is how you leave
Owners only. Removing the last owner removes the file.