# Get avatar

> `GET /v1/avatar` resolves a LinkedIn URL or username, then `302`-redirects to the CDN-cached profile photo. On a miss it returns a deterministic SVG fallback at the requested size.

```
GET https://avtrz.dev/v1/avatar
```

Authenticate with a publishable `pk_` key in the `key` query parameter, or any key in the `x-api-key` header. The OpenAPI spec is available at https://avtrz.dev/api/docs/openapi.

## Query parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `key` | string | Required | Your publishable (`pk_`) or secret (`sk_`) API key. May also be sent via the `x-api-key` header. |
| `linkedin_url` | string | Optional | Full LinkedIn profile URL, with or without scheme. One of `linkedin_url` or `username` is required. |
| `username` | string | Optional | LinkedIn handle (the path segment after `/in/`). |
| `size` | enum: 32 \| 64 \| 128 \| 256 \| 512 | Optional | Output square side, in pixels. Defaults to `128`. |
| `refresh` | boolean | Optional (deprecated) | Accepted for backwards compatibility but ignored. Avtrz refreshes stale avatars automatically. |

## Response

Successful lookups return `302 Found` with a `Location` header pointing at a CDN-cached image (WEBP or PNG, depending on the source). The redirect ships `Cache-Control: public, max-age=86400`, so browsers, proxies, and your CDN reuse it for 24 hours.

When no profile resolves, the endpoint streams a deterministic SVG fallback at the requested size with `Cache-Control: no-store`, so a transient miss cannot poison your cache.

## Status codes

| Status | Meaning |
| --- | --- |
| `302` | Profile resolved. The `Location` header points at the CDN image. |
| `200` | No profile resolved. SVG fallback returned in the body. |
| `400` | Invalid lookup parameters, or neither `linkedin_url` nor `username` supplied. |
| `401` | Missing or invalid API key, or a publishable key used from a domain not on its allow-list. |
| `402` | Monthly request or new-profile quota exceeded. Cached redirects keep serving. |
| `429` | Rate-limited for the calling IP. Honor `Retry-After` and back off. |
| `500` | Unexpected avatar lookup failure. |

Error responses (`400`, `401`, `402`, `429`, `500`) are JSON `{ "error": string }` and ship `Cache-Control: no-store`.

## Response headers

| Header | Description |
| --- | --- |
| `Cache-Control` | `public, max-age=86400` on a `302` redirect. `no-store` on SVG fallbacks and error responses. |
| `Location` | Present on a `302`. The CDN URL for the resolved image; safe to hand back to the browser. |
| `Retry-After` | Present on `429`. Seconds to wait before retrying. |

## Putting it together

A small Next.js route handler that proxies the avatar from your server. It keeps the secret key off the client, lets the browser follow the final redirect, and revalidates per the upstream cache headers.

```ts
import { NextResponse } from "next/server";

export async function GET(req: Request) {
  const linkedin_url = new URL(req.url).searchParams.get("linkedin_url");

  const upstream = await fetch(
    `https://avtrz.dev/v1/avatar?linkedin_url=${encodeURIComponent(linkedin_url!)}&size=128`,
    {
      headers: { "x-api-key": process.env.AVTRZ_SECRET! },
      redirect: "manual",
    }
  );

  const cdnUrl = upstream.headers.get("location");
  if (!cdnUrl) return NextResponse.json({ found: false });
  return NextResponse.redirect(cdnUrl, 302);
}
```
