> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nullpass.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Avatar Management

> Get and upload user avatar

## Endpoints

```
GET /api/avatar
POST /api/avatar
```

## GET /api/avatar

Retrieves the authenticated user's avatar image. Supports both local file storage and external URLs.

### Response

Returns the avatar image file with appropriate content type headers. If avatar is an external URL, returns a 302 redirect.

## POST /api/avatar

Uploads a new avatar image for the authenticated user. Old avatar is automatically deleted if it was a local file.

### Request

<ParamField body="avatar" type="file" required>
  Image file (multipart/form-data). Maximum size: 2MB. Allowed types: JPEG, PNG, WebP, GIF.
</ParamField>

### Response

<ResponseField name="avatar" type="string">
  Relative path to the uploaded avatar file
</ResponseField>

<ResponseField name="message" type="string">
  "Avatar uploaded successfully"
</ResponseField>

## Implementation Details

### Code Reference

```76:188:nullpass_clean/src/app/api/avatar/route.ts theme={null}
export async function GET(request: NextRequest) {
  const corsResponse = handleCors(request)
  if (corsResponse) return corsResponse

  const blocked = await protectRoute(request)
  if (blocked) return blocked

  const auth = await requireAuth(request)
  if ('error' in auth) return auth.error

  try {
    const avatarPath = await getUserAvatarPath(auth.userId)

    if (!avatarPath) {
      return errorResponse('Avatar not found', 404, request.headers.get('origin'))
    }

    if (avatarPath.startsWith('http://') || avatarPath.startsWith('https://')) {
      return Response.redirect(avatarPath, 302)
    }

    const fileBuffer = await fs.readFile(avatarPath)
    const ext = path.extname(avatarPath).toLowerCase()
    const contentType = 
      ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg' :
      ext === '.png' ? 'image/png' :
      ext === '.webp' ? 'image/webp' :
      ext === '.gif' ? 'image/gif' :
      'image/jpeg'

    return new Response(fileBuffer, {
      headers: {
        ...corsHeaders(request.headers.get('origin')),
        'Content-Type': contentType,
        'Cache-Control': 'public, max-age=3600',
      },
    })
  } catch (error) {
    logger.error('Get avatar error:', error)
    return errorResponse('Internal server error', 500, request.headers.get('origin'))
  }
}

export async function POST(request: NextRequest) {
  const corsResponse = handleCors(request)
  if (corsResponse) return corsResponse

  const blocked = await protectRoute(request)
  if (blocked) return blocked

  const auth = await requireAuth(request)
  if ('error' in auth) return auth.error

  try {
    const formData = await request.formData()
    const file = formData.get('avatar') as File | null

    if (!file) {
      return errorResponse('No file provided', 400, request.headers.get('origin'))
    }

    if (file.size > MAX_FILE_SIZE) {
      return errorResponse('File too large. Maximum size is 2MB', 400, request.headers.get('origin'))
    }

    if (!ALLOWED_MIME_TYPES.includes(file.type)) {
      return errorResponse(
        'Invalid file type. Allowed types: JPEG, PNG, WebP, GIF',
        400,
        request.headers.get('origin')
      )
    }

    await deleteOldAvatar(auth.userId)

    const userDir = await ensureUserAvatarDir(auth.userId)

    const ext = file.type === 'image/jpeg' ? '.jpg' :
                file.type === 'image/png' ? '.png' :
                file.type === 'image/webp' ? '.webp' :
                file.type === 'image/gif' ? '.gif' :
                '.jpg'
    
    const filename = `avatar_${Date.now()}${ext}`
    const filePath = path.join(userDir, filename)

    const arrayBuffer = await file.arrayBuffer()
    const buffer = Buffer.from(arrayBuffer)
    await fs.writeFile(filePath, buffer)

    const relativePath = `${auth.userId}/${filename}`
    await prisma.user.update({
      where: { id: auth.userId },
      data: { avatar: relativePath },
    })

    await createAuditLog(auth.userId, 'USER_UPDATE', {
      fields: ['avatar'],
    })

    return jsonResponse(
      {
        avatar: relativePath,
        message: 'Avatar uploaded successfully',
      },
      200,
      request.headers.get('origin')
    )
  } catch (error: any) {
    logger.error('Upload avatar error:', error)
    return errorResponse('Internal server error', 500, request.headers.get('origin'))
  }
}
```

## Status Codes

<ResponseField name="200" type="OK">
  Success
</ResponseField>

<ResponseField name="302" type="Found">
  Redirect to external avatar URL (GET only)
</ResponseField>

<ResponseField name="400" type="Bad Request">
  No file provided, file too large, or invalid file type
</ResponseField>

<ResponseField name="401" type="Unauthorized">
  Missing or invalid authentication token
</ResponseField>

<ResponseField name="404" type="Not Found">
  Avatar not found (GET only)
</ResponseField>

## File Requirements

* **Maximum size**: 2MB
* **Allowed formats**: JPEG, PNG, WebP, GIF
* **Storage**: Local file system (user-specific directories)

## Example Requests

### Get Avatar

```bash theme={null}
curl -X GET https://auth.nullpass.xyz/api/avatar \
  -H "Authorization: Bearer YOUR_TOKEN"
```

### Upload Avatar

```bash theme={null}
curl -X POST https://auth.nullpass.xyz/api/avatar \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -F "avatar=@/path/to/image.jpg"
```

## Environment Variables

<ResponseField name="AVATARS_PATH" type="string">
  Base path for avatar storage (default: `src/avatars`)
</ResponseField>

## Security Notes

* Old avatars are automatically deleted when uploading new ones
* External URLs are supported (redirects to URL)
* Files are stored in user-specific directories
* Content-Type headers are set based on file extension
* Cache-Control header set to 1 hour for GET requests


## OpenAPI

````yaml GET /avatar
openapi: 3.1.0
info:
  title: Null Pass API
  description: >-
    Internal API documentation for Null Pass authentication and service
    management system
  version: 1.0.0
servers:
  - url: https://auth.nullpass.xyz/api
security: []
paths:
  /avatar:
    get:
      tags:
        - Account
      summary: Get Avatar
      description: Retrieve user avatar
      responses:
        '200':
          description: Avatar image
          content:
            image/*: {}
      security:
        - bearerAuth: []
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

````