How to upload an image and use it
Image upload flow#
How an image gets from a local file to a stored field such as a person's profile_image_key.This is a workflow guide, not a reference: parameters, headers and error payloads for each
endpoint are documented in the Backoffice API reference.Two calls, two different keys#
No endpoint takes a binary and a resource in one call. Uploading and attaching are separate:1.
Upload sends the binary and returns a temporary key.
2.
Attach sends that key as a field of the resource, and returns the permanent key that was
stored.
① POST assets.<host>/upload/image ② PATCH /api/community/v2/people/:id
{ "profile_image_key": <key from ①> }
jane.jpg ──────────► uploads/<tenant_id>/<uuid>.jpg ──────────► <tenant_id>/community/<uuid>.jpg
temporary permanent, stored on the person
The key from step ① is not the key that ends up stored. Only the one returned by step ② is
durable — that is the one to persist on your side and to build display URLs from.Step ① — upload#
POST /upload/image on the assets host (assets.localhost:4000 in development,
assets.<cms-host> elsewhere), multipart/form-data, field named image. Every other call in
this workflow goes to the regular API host under /api.{ "data": { "key": "uploads/3f1c8a9e-…/9b7c1d2e-….jpg" } }
The field is key here; each resource that accepts it in step ② names its own field ..._key.This key is short-lived. Attach it in the same sequence of calls — do not store a uploads/…
key on your side to attach it later.Step ② — attach#
Send the key as the resource's image field. For a person's profile picture:The response carries a different key — the permanent one:{
"data": {
"id": "6a1b2c3d-…",
"first_name": "Jane",
"profile_image_key": "3f1c8a9e-…/community/4e5f6a7b-….jpg"
}
}
Replacing an image is the same call with a new uploaded key — nothing to delete on your side.Rules#
| Situation | Result |
|---|
The key was already attached somewhere (<tenant_id>/community/….jpg) | 400 — {"errors": {"profile_image_key": ["not uploaded file"]}}. A permanent key can never be attached again. |
| The uploaded key expired, or is a typo | 400 — {"errors": {"profile_image_key": ["not found"]}} |
| You update other fields and want to keep the current image | Omit the field. PATCH only applies what you send. |
| You want the same image on two resources | Upload it once per attach. |
The same two calls everywhere#
Step ① never changes. Step ② is whichever endpoint owns the field:| Resource | Field(s) | Attach endpoint |
|---|
| Person, Organization, Project, Event | profile_image_key, cover_image_key | POST /api/community/v2/workspaces/:workspace_id/<resources> (create) or PATCH /api/community/v2/<resources>/:id (update) |
| Asset library image | file_key | POST /api/core/v2/assets/images |
One exception. Ad creatives do both in a single call:
PUT /api/ad-ops/v2/creatives/:creative_id/upload takes the multipart image directly and sets
the creative's image_key. No intermediate key is returned.Attaching to the asset library instead#
Attaching directly to a resource suits a visual owned by that resource alone — a profile picture, a
cover. For an image that has to be reusable, searchable, captioned and credited, attach it to the
asset library instead: POST /api/core/v2/assets/images with the uploaded key plus name,
gravity and per-locale caption_html / credit. Same two calls, except you get back an Image
with an id you can reference.Posting a file already present in the library returns 409 Conflict with the existing image in
data — treat it as a success and use the id and file_key it returns.Displaying the image#
A stored key becomes a URL by prefixing it with the assets host. No API key is needed, and the
image is resized on the fly through quality (ld, md default, hd, original) and format
(webp, png, jpg, gif, otherwise negotiated from the Accept header):https://assets.<cms-host>/3f1c8a9e-…/community/4e5f6a7b-….jpg?quality=hd
End to end#
Modified at 2026-10-02 06:51:20