User accounts and profiles
Upload a user photo using multipart data
/a/User_updateImage
Replace a user avatar from a multipart file and mark the profile as having an image.
When to use this endpoint
Use the CRM2-style multipart route. It does not implement the data-URI branch of User_setImage; a string Filedata alone is not an uploaded file.
Authentication and permissions
Authenticate with a signed API request or logged-in CRM session. Session uploads require the normal dispatcher CSRF token when configured; neither photo route is exempt.
Requires login and an owned tenant user. Ordinary self-profile edits are allowed; editing another user requires client administrator or explicit Users Edit permission. Protected membership fields require that permission even for self. Configured TeamAccess record scope and relationship checks remain in force.
The dispatcher applies configured TeamAccess user-record scope before the handler. An explicit target outside that scope returns an error; omitted targets use the current user for the scope check. Active/deleted filtering and additional target checks are described below.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | Integer string | Yes | Existing positive user ID owned by the authenticated tenant. Missing, malformed, zero/negative or foreign-parent IDs are rejected before file storage. |
Filedata | Multipart file | Yes | File part named Filedata. PNG, JPEG, GIF or WebP are accepted up to 12 MiB and 40 million input pixels. A scalar data URI is not accepted by this multipart-only route. |
Example request
$upload = curl_init('https://your-crm-host/a/User_updateImage');
curl_setopt_array($upload, [
CURLOPT_POST=>true,
CURLOPT_RETURNTRANSFER=>true,
CURLOPT_COOKIE=>'PHPSESSID=replace-with-your-session-cookie',
CURLOPT_POSTFIELDS=>[
'id'=>'1042',
'_csrf'=>'replace-with-your-session-csrf-token',
'Filedata'=>new CURLFile('/path/to/avatar.png', 'image/png', 'avatar.png'),
],
]);
$result = json_decode(curl_exec($upload), true);
curl_close($upload);
Example response
{
"ok": 1,
"time": 1793700000
}
time is Unix seconds for cache busting, not an upload ID. Because a file part is required, this example describes multipart fields rather than a scalar FM_api call.
Errors and unusual responses
| Response | Condition |
|---|---|
{"error":"not logged in"} | No authenticated tenant context is available. Invalid signed credentials can be rejected by the dispatcher before the handler runs. |
{"error": "missing or invalid parameter: id"} | id is omitted or null. |
{"error": "no file uploaded"} | No multipart files are present. |
{"error": "failed to create image. please check file and upload again"} | No converted JPEG is produced. |
{"error": "user does not exist"} | No target user belongs to the authenticated account. |
{"error": "only client admins or users with Users edit permission can edit other users"} | Caller targets another user without explicit edit authority. |
{"error": "Unable to save user changes. Check the saved state before retrying."} | A checked write/transaction failed; inspect saved state before retrying, especially for separate global identity storage. |
{"error":"Unable to save user photo. Check the saved state before retrying."} | Storage did not acknowledge success or profile bookkeeping failed. |
{"error":"file too large. max file size is 12582912 bytes"} | Multipart source exceeds 12 MiB. |
Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.
Behaviour and edge cases
- Both routes use one checked photo helper and the existing User self/editor policy. The owned positive target must exist before conversion or storage.
- Validates multipart upload errors or strict data-URI/base64 input, file format, byte size and pixel count. ImageMagick converts the first frame, applies orientation and fits within 800×800 while preserving aspect ratio; this is not a square crop or a no-upscale guarantee.
- Uses private randomly named temporary storage and escaped conversion arguments. Temporary files are removed on conversion success, validation/storage failure and exceptions after temporary allocation.
- Stores the JPEG at
user-photos/<id>.jpgwith photo type 6. The file helper must acknowledge success with a positive file ID before has_image is set. A storage/profile-write failure returns an explicit error, never ok. - Both routes update has_image and user edit markers after confirmed file storage. A replacement file row can have a new ID. Photo storage and profile bookkeeping are not one distributed transaction: inspect saved state after an uncertain failure.
- Session requests use normal dispatcher CSRF checks. CRM2’s Dropzone already submits _csrf; integrations must send it when using an authenticated browser session.
Side effects
- Replaces photo data/metadata through the file helper, updates the tenant image marker/edit timestamps, clears files/users caches and logs image updated on success. No global identity photo is copied and no message is sent.
See it in Fieldmotion
CRM1
No direct production caller was found in CRM1. Its User Main avatar uploader uses the separate User_setImage route.
CRM2
Open a User avatar widget in CRM2 and upload a photo. Filter User_updateImage: widgetDropzone posts multipart Filedata and id to /php/api.php with extra=/User_updateImage, then refreshes the avatar cache.
Related endpoints
User_setImage— Use the data-URI photo path, with its documented multipart limitation.User_setName— Update the user’s display name.