---
title: Uploading contact avatars | Lightfield
description: Upload an image and set it as a contact's profile photo in Lightfield.
---

Use the [file upload lifecycle](/using-the-api/file-uploads/index.md) to upload an image, then set it as the contact’s photo through the contact’s `$profilePhotoUrl` field. The photo is the one the app shows on the contact, the same as choosing **Upload photo** on the record.

## Before you begin

You’ll need:

- A valid [API key](/using-the-api/api-keys/index.md) that resolves to a user. Workspace keys use their creator. Keys that cannot resolve to a user get `purpose_requires_user` when they create the upload.
- The `files:create` scope to upload the image
- Either `contacts:create` or `contacts:update`, depending on whether you are creating a new contact or updating an existing one
- Edit access to the contact. Viewers and members who can’t edit the contact can’t change its photo in the app or through the API.
- Optional `files:read` and `contacts:read` scopes if you want to check the upload or the final photo

Only the user who opened an upload can set it as a photo, and each upload can be used once. Upload the image again for each contact, using a key for the same user that will write the contact.

## How setting a photo works

There are two supported patterns:

| Pattern       | When to use                                                  | Contact request                                         |
| ------------- | ------------------------------------------------------------ | ------------------------------------------------------- |
| Set on create | You are creating the contact and its photo together          | `POST /v1/contacts` with `fields.$profilePhotoUrl`      |
| Set on update | The contact already exists, or you want to replace its photo | `POST /v1/contacts/{id}` with `fields.$profilePhotoUrl` |

In both patterns, the first three steps are the [file upload lifecycle](/using-the-api/file-uploads/index.md):

1. `POST /v1/files` with `purpose: "contact_avatar"`
2. `PUT <uploadUrl>`
3. `POST /v1/files/{id}/complete`

After that, you set the file ID as the contact’s photo.

## Step 1: Upload the image

Create the upload session with `purpose: "contact_avatar"`. The image must be a PNG, JPEG, WebP or GIF of at most 8 MB, the same limits as [photos uploaded in the app](/help/settings/profile/index.md#change-your-profile-photo).

Set `sizeBytes` to the file’s exact size in bytes. The upload URL is signed for that length, so a file of any other size is rejected by storage.

Terminal window

```
curl https://api.lightfield.app/v1/files \
  -X POST \
  -H "Authorization: Bearer $LIGHTFIELD_API_KEY" \
  -H "Lightfield-Version: 2026-03-01" \
  -H "Content-Type: application/json" \
  -d '{
        "purpose": "contact_avatar",
        "filename": "ada-chen.png",
        "mimeType": "image/png",
        "sizeBytes": 48213
      }'
```

Save `id` as `FILE_ID` and `uploadUrl` as `UPLOAD_URL`, then upload the bytes with the returned `uploadHeaders`:

Terminal window

```
curl -i -X PUT "$UPLOAD_URL" \
  -H "content-type: image/png" \
  --data-binary @ada-chen.png
```

Then complete the upload:

Terminal window

```
curl https://api.lightfield.app/v1/files/$FILE_ID/complete \
  -X POST \
  -H "Authorization: Bearer $LIGHTFIELD_API_KEY" \
  -H "Lightfield-Version: 2026-03-01" \
  -H "Content-Type: application/json" \
  -d '{}'
```

Check that the response has `status: "COMPLETED"`. See [Uploading files](/using-the-api/file-uploads/index.md) for checksums, retries and upload errors.

## Option A: create a contact with a photo

Pass the completed file ID in `fields.$profilePhotoUrl` when you [create the contact](/api/resources/contact/methods/create/index.md).

Terminal window

```
curl https://api.lightfield.app/v1/contacts \
  -X POST \
  -H "Authorization: Bearer $LIGHTFIELD_API_KEY" \
  -H "Lightfield-Version: 2026-03-01" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: create-ada-chen-001" \
  -d '{
        "fields": {
          "$name": { "firstName": "Ada", "lastName": "Chen" },
          "$email": ["ada@northwind.example"],
          "$profilePhotoUrl": "fil_abc123"
        }
      }'
```

## Option B: set or replace the photo on an existing contact

[Update the contact](/api/resources/contact/methods/update/index.md) with the completed file ID. The new photo replaces any photo uploaded earlier, in the app or through the API.

Terminal window

```
curl https://api.lightfield.app/v1/contacts/$CONTACT_ID \
  -X POST \
  -H "Authorization: Bearer $LIGHTFIELD_API_KEY" \
  -H "Lightfield-Version: 2026-03-01" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: ada-chen-photo-001" \
  -d '{
        "fields": {
          "$profilePhotoUrl": "fil_abc123"
        }
      }'
```

Choose a new idempotency key for each intended write and reuse it only when retrying the same request. A retried update with the same key returns the original result without setting the photo again. See [Idempotency](/using-the-api/idempotency/index.md).

## Remove a photo

Set `$profilePhotoUrl` to `null`. This works like **Remove photo** in the app and removes the photo the contact currently shows. If that’s an uploaded photo, a photo synced earlier (for example, from LinkedIn) can appear in its place; send `null` again to hide that one too.

Terminal window

```
curl https://api.lightfield.app/v1/contacts/$CONTACT_ID \
  -X POST \
  -H "Authorization: Bearer $LIGHTFIELD_API_KEY" \
  -H "Lightfield-Version: 2026-03-01" \
  -H "Content-Type: application/json" \
  -d '{ "fields": { "$profilePhotoUrl": null } }'
```

## Image URLs are not uploads

`$profilePhotoUrl` sets an uploaded photo only from a `fil_` ID. Apart from LinkedIn and Google profile-picture links, an image URL is not fetched: the request succeeds, but the photo does not change. To use an image hosted elsewhere, download it and upload it with the steps above.

Writing back the URL a read returned for the current photo is treated as no change, so you can send a retrieved record back unmodified.

## Verifying the photo

[Retrieve the contact](/api/resources/contact/methods/retrieve/index.md) and read `fields.$profilePhotoUrl.value`. It’s a temporary URL for the photo the app shows, not the file ID you sent, so fetch a fresh record instead of storing it.

Terminal window

```
curl https://api.lightfield.app/v1/contacts/$CONTACT_ID \
  -H "Authorization: Bearer $LIGHTFIELD_API_KEY" \
  -H "Lightfield-Version: 2026-03-01"
```

## Common errors

A photo that can’t be used fails the whole contact request with `422` `unprocessable_content`, and `param` is `$profilePhotoUrl`. None of the request’s other fields are written. Fix the photo and send the request again.

| Message includes                                 | Cause and fix                                                                                                                                                                                                                                                          |
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `no contact avatar upload found with ID`         | The ID isn’t a `contact_avatar` upload opened by this key’s user, the upload expired before it was completed, or it was already used on a contact. Upload the image again as the same user. Retrying an idempotent create after its upload was used also returns this. |
| `the file is not a PNG, JPEG, WebP or GIF image` | The uploaded bytes don’t match the declared image type. Upload a real PNG, JPEG, WebP or GIF with its matching `mimeType`.                                                                                                                                             |

At upload time, `invalid_mime_type` and `file_too_large` mean the image isn’t a supported type or is over 8 MB. A `404` on the contact means it doesn’t exist or isn’t in your workspace. See [Errors](/using-the-api/errors/index.md) for the standard error envelope.

## Next steps

- [Uploading files](/using-the-api/file-uploads/index.md): reference guide for the underlying file upload lifecycle.
- [Create, update and restore records](/using-the-api/manage-records/index.md): the rest of the contact write flow.
- [API Reference](/api/index.md): full endpoint reference for files, contacts and other resources.

## Related documentation

- In the app: [change a contact’s photo](/help/contacts/index.md#change-a-contacts-photo).
- Endpoint contracts: [create a file upload](/api/resources/file/methods/create/index.md), [create a contact](/api/resources/contact/methods/create/index.md) and [update a contact](/api/resources/contact/methods/update/index.md).
