# Bluesky avatar API: getting a profile picture from a handle

> Bluesky's public AppView API returns avatars without authentication. Here's how to call it, and when a plain image URL is simpler.

Published 2026-10-19 · https://avatarsapi.com/blog/bluesky-avatar-api

Bluesky is the friendliest major platform for this job: its public API returns profile data, avatar included, without a login.

## The public API

```sh
curl "https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=bsky.app"
```

The JSON response has an `avatar` field with a URL on `cdn.bsky.app`. No token is needed for public profiles ([example](https://alexwlchan.net/til/2025/bluesky-avatar-url)). `actor` takes a handle (`jay.bsky.team`, or a custom domain handle) or a DID. To fetch several at once, `app.bsky.actor.getProfiles` takes a list ([docs](https://docs.bsky.app/docs/api/app-bsky-actor-get-profiles)).

Two things to handle yourself:

- **It is JSON, not an image.** You make a request, read `avatar`, then render that URL. In a browser that means a fetch before the image can start loading.
- **Avatars change.** The CDN URL includes the image's content id, so a new picture means a new URL. Cache the lookup, not the URL forever.

If you are already building on AT Protocol, use the API directly.

## One image URL

```html
<img src="https://avatarsapi.com/bluesky/bsky.app" alt="">
```

avatarapi does the lookup on a cache miss and answers with the image itself, so it goes straight into `src` with no fetch step. It is cached at the edge for 7 days by default, and the URL stays the same when the picture changes.

- `?size=16..1000` sets the square size.
- A handle that does not resolve returns a 404 with `"code": "ENOTFOUND"`, so `onerror` can show your placeholder.

## When the image URL wins

Use it when your app shows people from several networks. The same pattern covers [Mastodon](/platforms/mastodon) (`/mastodon/user@instance`), [Twitch](/platforms/twitch), [X](/blog/get-x-twitter-profile-picture), and [GitHub](/blog/github-avatar-url), so one `<img>` component handles all of them.
