---
title: Upload an avatar image, get back its public URL
description: The bytes-to-URL lane every avatar field depends on.
api_method: POST
api_path: "/v1/uploads/avatars"
canonical_url: https://wiblo.app/docs/developers/api/identity/upload-avatar
last_updated: 2026-08-03T21:24:33+02:00
md_url: https://wiblo.app/docs/developers/api/identity/upload-avatar.md
---

# Upload an avatar image, get back its public URL

`POST /v1/uploads/avatars`

The bytes-to-URL lane every avatar field depends on. `multipart/form-data` with a single `file` part — PNG, JPEG, or WebP, 10 MiB cap, validated from magic bytes (the declared content type is never trusted). The image lands in the public-read `avatars` bucket under a path keyed by the caller's user id, and the response carries the canonical URL to store via any avatar-bearing PATCH (`/v1/me`, workspace, member). Per-user rate-limited at 10/min.

## Request

**curl**

```bash
curl https://api.wiblo.app/v1/uploads/avatars \
  -X POST \
  -H "Authorization: Bearer $WIBLO_TOKEN"
```

**TypeScript**

```ts
import { createSdk, uploadAvatar } from "@workspace/sdk"

const sdk = createSdk({ baseUrl: "https://api.wiblo.app" })

const { data, error } = await uploadAvatar({
  client: sdk,
})
```

## Responses

**`201`** — The stored image's public URL. Returns `UploadAvatarResponse`.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | `string` | Yes |  |

**`400`** — No `file` part, the bytes are not a supported image, or the file exceeds 10 MiB (`VALIDATION_FAILED`). Returns `ApiErrorEnvelope`.

**`401`** — No valid Supabase session cookie was present. Returns `ApiErrorEnvelope`.

**`429`** — Rate limit exceeded. The body's `error.code` is `RATE_LIMITED` and `error.details.retry_after` is the same number of seconds as the `Retry-After` header. Returns `ApiErrorEnvelope`.
