Developer API: cloud saves
Let players sign in to your game or app with their Synkrown Hub account and keep their saves in the cloud. It works for builds hosted here and for games and apps on your own website.
1. Turn it on
- Open your page in My pages and go to the Cloud saves tab.
- Tick Turn on cloud saves and note your App ID.
- Optional: tick Only for owners of the full version. Players who have not bought it are then refused.
- Only if your game runs on another website: add the address players come back to after signing in, for example
https://mygame.com/play. For testing,http://localhost:8080/works too.
2. Add the script
<script src="https://synkrown.org/js/synkrown.js"></script>Use this full address for builds hosted here too.
3. Sign players in
const hub = SynKrown.init({ app: 123 }); // your App ID
await hub.ready;
if (hub.user) {
console.log('Signed in as', hub.user.display_name);
} else if (hub.error === 'no_access') {
// They need the full version.
} else {
signInButton.onclick = () => hub.signIn();
}- Hosted here: players who are signed in on the hub are signed in straight away.
hub.errorislogin_requiredwhen they are not, and the player bar shows a sign in link. - Your own website:
hub.signIn()takes the player to the hub to sign in and agree, then back to your page.hub.readyfinishes the sign in when they return. The sign in is kept for 30 days. Sign in from a title screen, since the page reloads.
4. Save and load
await hub.saves.put('slot1', { level: 3, coins: 120 });
const save = await hub.saves.get('slot1'); // null when empty
console.log(save.data.level, save.version);
// Projects and other files: pass a Blob, File, ArrayBuffer or typed array.
await hub.saves.put('project', projectBlob);
const project = await hub.saves.get('project'); // project.data is a Blob
const list = await hub.saves.list(); // [{ slot, content_type, bytes, version, updated_at }]
await hub.saves.remove('slot1');
hub.signOut();- Plain values and objects are saved as JSON and come back the same. Files and binary data are saved as they are and come back as a
Blob. - Up to 90 MB per save and 20 saves per player. For a bigger project, split it over several slots.
- Each player has 500 MB of cloud save space (more with a storage plan), shared by every game they use. When it runs out, saving fails with
storage_fulland the player is shown a popup with a link to their storage page, where they can see what is using the space and free some up. A save that does not get bigger is always allowed. To show your own message instead, useSynKrown.init({ app: 123, storagePopup: false }).await hub.saves.usage()gives{ used, limit }in bytes, if you want to show a meter. - Slot names use letters, numbers,
.,-and_, up to 64 characters. - To avoid one device overwriting another, pass the version you loaded:
hub.saves.put('slot1', data, { version: save.version }). If the save changed in the meantime this fails withversion_conflict. Use{ version: 0 }for "only if empty".
5. KrownOS desktop (optional)
Every member has a desktop at KrownOS. Your game or app can put things there, so players see their work and saves next to everything else.
// Show a save as an icon on their desktop. Opening it opens your game.
await hub.saves.put('slot1', data, { desktop: 'My castle' });
// Save a file to their desktop, or to a folder there named after it.
const file = await hub.desktop.save('Song.json', song, { folder: 'My songs' });
const files = await hub.desktop.list(); // only the files your app made
const loaded = await hub.desktop.load(file.id); // { data, version }
await hub.desktop.update(file.id, newSong, { version: loaded.version });
await hub.desktop.remove(file.id);
// Opened from an icon on the desktop?
if (hub.launch && hub.launch.file) openSong(await hub.desktop.load(hub.launch.file));
if (hub.launch && hub.launch.save) loadSlot(hub.launch.save);- Files work like saves: JSON comes back as the value, anything else as a
Blob. Up to 25 MB each. - Desktop files count towards the same cloud save space as saves.
- Your app only ever sees the files it made. Players can rename, move and delete them like any other file.
hub.launchis set for builds hosted here, when a player opens your app from one of its icons.
Your data stays yours
Saves are stored per app and per player. The app is taken from the player's sign in, never from the request, so another developer using the same slot names, or even your App ID, can never read or overwrite your players' saves. Players can disconnect an app, and delete its saves, from My account.
Errors
Failed calls throw an error with a code:
login_required,invalid_token: sign in again.no_access: the player needs the full version.access_denied: the player said no.version_conflict: the save changed;error.versionis the current version.storage_full: the player is out of space;error.usedanderror.limitare in bytes.too_large,too_many_slots,bad_slot,rate_limited.
HTTP API
For engines without JavaScript. Send Authorization: Bearer TOKEN with every call. Replies are JSON, apart from reading a save.
GET /api/v1/me: the player and your app.GET /api/v1/saves: every save, without the data, plusstorage(space used and the limit).GET /api/v1/saves/SLOT: the save itself, exactly as it was sent, with itsContent-Type. The version and last change time are in theX-Save-VersionandX-Save-Updatedheaders.PUT /api/v1/saves/SLOT?version=N: the body is the save, any type, withContent-TypeandContent-Lengthset.versionis optional.DELETE /api/v1/saves/SLOTPUT /api/v1/saves/SLOT?desktop=NAME: also shows the save on the player's KrownOS desktop.GET /api/v1/files: the desktop files your app made, plusstorage.POST /api/v1/files?name=NAME&folder=FOLDER: a new desktop file. The body is the file;folderis optional. The reply is{ "id", "name", "version" }.GET /api/v1/files/ID,PUT /api/v1/files/ID?version=NandDELETE /api/v1/files/IDwork like the save calls.
Getting a token is standard OAuth with PKCE (S256):
- Send the player to
https://synkrown.org/api/connect?app=ID&redirect_uri=URL&state=RANDOM&code_challenge=CHALLENGE. - They come back to
URL?code=...&state=..., or?error=.... - Within 5 minutes,
POST /api/v1/tokenwith JSON{ "code", "code_verifier", "redirect_uri" }. The reply is{ "token", "expires_at" }.