Bumbag

API Docs

Bumbag's own clients are built on this same API. If you're writing a client for a device Bumbag doesn't ship for, everything you need is here.

1. The basics

Everything is JSON over HTTPS and lives under /api/v1. These are open to anyone, with no token:

Every other endpoint needs an access token. A machine-readable schema is served at /openapi.json when the operator runs the server in debug mode.

2. Getting a token

Register (or use an existing account), then log in. Send the token back as a bearer credential on every later call.

Then send Authorization: Bearer <access_token>. Access tokens are short-lived, so keep the refresh token and renew before it expires.

3. Pairing a device

Hardware with no keyboard of its own pairs in three steps. The device has no account, so possessing the code is the credential — the code is what the device holds while it waits for you to approve it in the web app.

Asking for a code and claiming one are unauthenticated by design — the device is holding the code and nothing else, so the code is what authorises it. GET /api/v1/pair/{code} is open for the same reason, so a device can show "waiting for approval" before it has a token. Approving is the one step of the three that needs your token, because that is the step that attaches a device to your account. A code expires ten minutes after it is issued.

Send X-Device-Id on later calls. It's how the server tells your devices apart, so it can record where each one keeps a save and apply per-device folder choices. Claiming a code also lets you re-pair a device you've lost without losing its history.

4. What you can call

5. Uploading a save

POST /api/v1/saves/upload takes multipart/form-data with a file part and an optional system and device_label. Leave system off and the server tries to work it out from the filename and extension. If it genuinely can't, the upload is rejected with 422 and the error lists the systems that file's extension could belong to — send one of those back as system and it goes through. If you would rather store it first and decide later, send system=UNCLASSIFIED explicitly and categorize it afterwards with PATCH /saves/{id}.

Uploads are keyed on the filename. Re-uploading byte-identical content under a name you already have is a harmless no-op. Different content under an existing name does not overwrite what you had: the first divergence is accepted and comes back 201 with conflict_created: true, so the upload is not lost — resolve the conflict and retry. If you re-upload that same name again while a decision is still outstanding, you get 409. Save states and emulator configs are refused: they aren't portable between emulators.

6. Limits and conventions