Skip to content

Skill for unavatar-api

Resolve user avatars from 25+ platforms via a single API endpoint.

User intent

  • display profile pictures from GitHub
  • X/Twitter
  • Instagram
  • Apple Music
  • other platforms
  • look up avatars by email/username/domain/phone number

Installation

npx skills add https://github.com/microlinkhq/skills --skill unavatar-api
# unavatar API

unavatar.io resolves user avatars from 25+ platforms via a single endpoint. Supports lookup by email, username, domain, or phone number.

## Endpoint

`https://unavatar.io`

## Quick Start

All lookups use the `/:provider/:key` format:

| Input    | Pattern                      | Example                                          |
| -------- | ---------------------------- | ------------------------------------------------ |
| email    | `/provider/[email protected]` | `https://unavatar.io/gravatar/[email protected]` |
| username | `/provider/username`         | `https://unavatar.io/github/kikobeats`           |
| domain   | `/provider/domain.com`       | `https://unavatar.io/google/reddit.com`          |
| phone    | `/provider/phonenumber`      | `https://unavatar.io/whatsapp/34612345678`       |

## Authentication

- **Free**: No auth required, 50 requests/day per IP
- **Pro**: Pass `x-api-key` header, usage-based billing at $0.001/token

```bash
curl -H "x-api-key: YOUR_API_KEY" https://unavatar.io/instagram/kikobeats
```

### Proxy Tiers & Token Cost

| Proxy tier  | Tokens |  Cost  |
| ----------- | :----: | :----: |
| Origin      |   1    | $0.001 |
| Datacenter  |   +2   | $0.003 |
| Residential |   +4   | $0.007 |

Response headers: `x-proxy-tier`, `x-unavatar-cost`, `x-pricing-tier`.

### Key Management

- **Usage**: `curl -H "x-api-key: KEY" https://unavatar.io/key/usage`
- **Rotate**: `curl -H "x-api-key: KEY" https://unavatar.io/key/rotate`

## Query Parameters

### ttl

- Type: `number` | `string`
- Default: `'24h'`
- Range: `'1h'` to `'28d'`

Cache duration for the resolved avatar. The avatar is considered fresh until TTL expiration, then recomputed on next request.

e.g., `https://unavatar.io/kikobeats?ttl=1h`

### fallback

- Type: `string` | `boolean`

Custom fallback image when avatar can't be resolved. Accepts:

- URL to external avatar service (boringavatars.com, avatar.vercel.sh)
- URL to static image
- Base64 encoded image (e.g., transparent 1x1 pixel GIF)
- `false` to disable fallback (returns 404 instead)

```
https://unavatar.io/github/37t?fallback=https://avatar.vercel.sh/37t?size=400
https://unavatar.io/github/37t?fallback=false
https://unavatar.io/github/37t?fallback=data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==
```

### json

- Type: `boolean`

Returns JSON payload instead of image content.

e.g., `https://unavatar.io/kikobeats?json`

## Response Format

When `json=true` is passed, responses follow a predictable shape:

| Field   | Type         | Present in                | Description                                     |
| ------- | ------------ | ------------------------- | ----------------------------------------------- |
| status  | string       | all JSON responses        | One of: `success`, `fail`, `error`.             |
| message | string       | all JSON responses        | Human-readable summary for display/logging.     |
| data    | object       | success                   | Response payload for successful requests.       |
| code    | string       | fail, error               | Stable machine-readable error code.             |
| more    | string (URL) | most fail/error responses | Documentation URL with troubleshooting details. |
| report  | string       | some error responses      | Support contact channel (e.g., mailto:).        |

## Response Headers

| Header                 | Purpose                                                 |
| ---------------------- | ------------------------------------------------------- |
| x-pricing-tier         | `free` or `pro` — the plan used for this request        |
| x-timestamp            | Server timestamp when request was received              |
| x-unavatar-cost        | Token cost of the request (avatar routes only)          |
| x-proxy-tier           | Proxy tier used: `origin`, `datacenter`, or `residential` |
| x-rate-limit-limit     | Maximum requests allowed per window (free tier only)    |
| x-rate-limit-remaining | Remaining requests in current window (free tier only)   |
| x-rate-limit-reset     | UTC epoch seconds when window resets (free tier only)   |
| retry-after            | Seconds until rate limit resets (only on 429 responses) |

## Response Errors

Client-side issues return `status: "fail"` (HTTP 4xx). Service-side issues return `status: "error"` (HTTP 5xx).

| HTTP | Code               | Typical trigger                           |
| ---- | ------------------ | ----------------------------------------- |
| 400  | ESESSIONID         | Missing session_id in /checkout/success   |
| 400  | ESESSION           | Checkout session not paid or not found    |
| 400  | ESIGNATURE         | Missing stripe-signature header           |
| 400  | EWEBHOOK           | Invalid/failed Stripe webhook processing  |
| 400  | EAPIKEYVALUE       | Missing apiKey query parameter            |
| 400  | EAPIKEYLABEL       | Missing label query parameter             |
| 401  | EEMAIL             | Invalid or missing authenticated email    |
| 401  | EUSERUNAUTHORIZED  | Missing/invalid auth for protected routes |
| 401  | EAPIKEY            | Invalid x-api-key                         |
| 403  | ETTL               | Custom ttl requested without pro plan     |
| 403  | EPRO               | Provider restricted to pro plan           |
| 404  | ENOTFOUND          | Route not found                           |
| 404  | EAPIKEYNOTFOUND    | API key not found                         |
| 409  | EAPIKEYEXISTS      | Custom API key already exists             |
| 409  | EAPIKEYLABELEXISTS | API key label already exists              |
| 409  | EAPIKEYMIN         | Attempt to remove last remaining key      |
| 429  | ERATE              | Free-tier daily rate limit exceeded       |
| 500  | ECHECKOUT          | Stripe checkout session creation failed   |
| 500  | EAPIKEYFAILED      | API key retrieval after checkout failed   |
| 500  | EINTERNAL          | Unexpected internal server failure        |

## Providers

Providers are grouped by input type.

| Provider      | email | username | domain | phone |
| ------------- | :---: | :------: | :----: | :---: |
| Apple Music   |       |    ✓     |        |       |
| Bluesky       |       |    ✓     |        |       |
| DeviantArt    |       |    ✓     |        |       |
| Dribbble      |       |    ✓     |        |       |
| DuckDuckGo    |       |          |   ✓    |       |
| GitHub        |       |    ✓     |        |       |
| GitLab        |       |    ✓     |        |       |
| Google        |       |          |   ✓    |       |
| Gravatar      |   ✓   |          |        |       |
| Instagram     |       |    ✓     |        |       |
| Microlink     |       |          |   ✓    |       |
| OnlyFans      |       |    ✓     |        |       |
| OpenStreetMap |       |    ✓     |        |       |
| Patreon       |       |    ✓     |        |       |
| Reddit        |       |    ✓     |        |       |
| SoundCloud    |       |    ✓     |        |       |
| Spotify       |       |    ✓     |        |       |
| Substack      |       |    ✓     |        |       |
| Telegram      |       |    ✓     |        |       |
| TikTok        |       |    ✓     |        |       |
| Twitch        |       |    ✓     |        |       |
| Vimeo         |       |    ✓     |        |       |
| WhatsApp      |       |          |        |   ✓   |
| X/Twitter     |       |    ✓     |        |       |
| YouTube       |       |    ✓     |        |       |

### URI Format Providers

**Apple Music** supports `type:id` format. Without explicit type, it searches `artist` then `song`:

- by name: `/apple-music/daft%20punk`
- `artist`: `/apple-music/artist:daft%20punk` or `/apple-music/artist:5468295`
- `album`: `/apple-music/album:discovery` or `/apple-music/album:78691923`
- `song`: `/apple-music/song:harder%20better%20faster%20stronger` or `/apple-music/song:697195787`

**Spotify** supports `type:id` format (default type: `user`):

- `user`: `/spotify/kikobeats`
- `artist`: `/spotify/artist:6sFIWsNpZYqbRiDnNOkZCA`
- `playlist`: `/spotify/playlist:37i9dQZF1DXcBWIGoYBM5M`
- `album`: `/spotify/album:4aawyAB9vmqN3uQ7FjRGTy`
- `show`: `/spotify/show:6UCtBYL29hRg064d4i5W2i`
- `episode`: `/spotify/episode:512ojhOuo1ktJprKbVcKyQ`
- `track`: `/spotify/track:11dFghVXANMlKmJXsNCbNl`

**WhatsApp** supports `type:id` format (default type: `phone`):

- `phone`: `/whatsapp/34612345678`
- `channel`: `/whatsapp/channel:0029VaABC1234abcDEF56789`
- `chat`: `/whatsapp/chat:ABC1234DEFghi`
- `group`: `/whatsapp/group:ABC1234DEFghi`

**YouTube** accepts handle, legacy username, or channel ID. Input starting with `UC` and 24 characters long is treated as a channel ID:

- handle: `/youtube/casey` or `/youtube/@casey`
- channel ID: `/youtube/UC_x5XG1OV2P6uZZ5FSM9Ttw`

For individual provider examples, see [providers.md](providers.md).