# Uploads and media

URL: https://docs.spacerun.co/docs/platform/guides/uploads-and-media

> Use signed avatar and staff storage uploads, and upload game artwork as multipart files.

Use a user JWT for avatars, or staff permissions for storage/game artwork. Examples use [apiRequest](https://docs.spacerun.co/docs/platform/guides/quickstart). Storage URLs are distinct from the Platform API base URL.

## Upload and confirm an avatar

1. Choose PNG, JPEG or WebP and request [createAvatarUploadUrl](https://docs.spacerun.co/docs/platform/reference/profile/createAvatarUploadUrl) with its `contentType`.
2. Upload the bytes to the returned signed `uploadUrl`.
3. Call [confirmAvatar](https://docs.spacerun.co/docs/platform/reference/profile/confirmAvatar) with exactly the returned object `path`.

```ts
// file is a File selected by the user.
const target = await apiRequest("/v1/me/avatar/upload-url", {
  method: "POST", body: JSON.stringify({ contentType: file.type }),
});
const upload = await fetch(target.uploadUrl, {
  method: "PUT", headers: { "Content-Type": file.type }, body: file,
});
if (!upload.ok) throw new Error(`Upload failed: ${upload.status}`);
const avatar = await apiRequest("/v1/me/avatar/confirm", {
  method: "POST", body: JSON.stringify({ path: target.path }),
});
console.log(avatar.avatar_url);
```

The signed URL contains the upload authorization; do not replace it with your Platform API URL or add a service-role key. For clients using the Supabase signed-upload method instead of raw PUT, use the returned upload token and path as specified by that client's storage method.

Confirmation rejects paths outside the caller's folder or objects that were not uploaded. It also removes the caller's other avatar objects. If the signed upload expires or fails, request a fresh target; do not confirm a failed upload.

## Staff storage

With `storage.read`, browse buckets/objects and request previews. With `storage.write`, [createStorageUploadUrl](https://docs.spacerun.co/docs/platform/reference/storage/createStorageUploadUrl) or [replaceStorageObjectUrl](https://docs.spacerun.co/docs/platform/reference/storage/replaceStorageObjectUrl) issues a signed target. Upload outside the Platform API as above. Deletion uses `storage.delete`; bucket configuration uses `storage.configure`. Browse results use limit/offset metadata, not the page/total envelope.

## Game artwork is multipart

Game artwork requires `store.write` and a real multipart `file` field:

```bash
curl -X PUT 'https://api.example.com/v1/admin/store/games/1/media/cover' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -F 'file=@./cover.png'
```

Replace game ID and artwork kind with valid values from [putStoreGameMedia](https://docs.spacerun.co/docs/platform/reference/store-game-art/putStoreGameMedia). The client supplies multipart boundaries; do not set JSON content type. Artwork supports PNG/JPEG/WebP with an 8 MiB file limit plus per-slot dimensions/aspect ratios. [addStoreGameScreenshot](https://docs.spacerun.co/docs/platform/reference/store-game-art/addStoreGameScreenshot) requires a 16:9 image of at least 1920x1080 and a maximum of 12 screenshots per game.

Missing files return application `400 validation_failed`; invalid parameters can return a validator-shaped 400. Oversized request bodies return 413. Format, dimensions and duplicate/limit failures are documented per operation; inspect the body before retrying. Signed storage upload and multipart artwork upload are different workflows.
