Uploading contact avatars
Upload an image and set it as a contact's profile photo in Lightfield.
Use the file upload lifecycle 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
Section titled “Before you begin”You’ll need:
- A valid API key that resolves to a user. Workspace keys use their creator. Keys that cannot resolve to a user get
purpose_requires_userwhen they create the upload. - The
files:createscope to upload the image - Either
contacts:createorcontacts: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:readandcontacts:readscopes if you want to check the upload or the final photo
How setting a photo works
Section titled “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:
POST /v1/fileswithpurpose: "contact_avatar"PUT <uploadUrl>POST /v1/files/{id}/complete
After that, you set the file ID as the contact’s photo.
Step 1: Upload the image
Section titled “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.
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.
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:
curl -i -X PUT "$UPLOAD_URL" \ -H "content-type: image/png" \ --data-binary @ada-chen.pngThen complete the upload:
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 for checksums, retries and upload errors.
Option A: create a contact with a photo
Section titled “Option A: create a contact with a photo”Pass the completed file ID in fields.$profilePhotoUrl when you create the contact.
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
Section titled “Option B: set or replace the photo on an existing contact”Update the contact with the completed file ID. The new photo replaces any photo uploaded earlier, in the app or through the API.
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.
Remove a photo
Section titled “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.
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
Section titled “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
Section titled “Verifying the photo”Retrieve the contact 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.
curl https://api.lightfield.app/v1/contacts/$CONTACT_ID \ -H "Authorization: Bearer $LIGHTFIELD_API_KEY" \ -H "Lightfield-Version: 2026-03-01"Common errors
Section titled “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 for the standard error envelope.
Next steps
Section titled “Next steps”- Uploading files: reference guide for the underlying file upload lifecycle.
- Create, update and restore records: the rest of the contact write flow.
- API Reference: full endpoint reference for files, contacts and other resources.
Related documentation
Section titled “Related documentation”- In the app: change a contact’s photo.
- Endpoint contracts: create a file upload, create a contact and update a contact.