invoke-ai/InvokeAI · error · HTTPException
Not authorized to modify this image
Error message
Not authorized to modify this image
What it means
`assert_image_owner` raises this 403 when a non-admin user attempts to mutate an image they do not own. Ownership passes only if the user owns the image row, owns the image's board, or the image sits on a Public board; otherwise the mutation is refused.
Source
Thrown at invokeai/app/api/routers/_access.py:54
# aggregates — five extra queries and five extra ways to fail per name.
#
# Only a board positively known to be gone falls through to the 403. A storage error
# propagates instead of being caught here: `board_records.get` deliberately does not
# translate sqlite errors into not-found, and a caller that cannot decide ownership
# must not report the name as an ordinary permission denial — the batch loops treat a
# 403 as a silent auth skip, which turned a locked database into images dropped from
# the response with no failure reported at all.
try:
board = ApiDependencies.invoker.services.board_records.get(board_id)
except BoardRecordNotFoundException:
pass
else:
if board.user_id == current_user.user_id:
return
if board.board_visibility == BoardVisibility.Public:
return
raise HTTPException(status_code=403, detail="Not authorized to modify this image")
def _assert_image_record_exists(image_name: str) -> None:
"""Turn a refusal into a 404 when the image is positively gone.
The two refusals mean opposite things to a client holding a reference to the image — a
workflow's image field, a reference image on a canvas layer. Gone is permanent, and the
reference should be dropped. Denied is a permission decision that can be reversed (a board
flipped back to Shared, an owner re-granting access), and dropping the reference over one
destroys work the user cannot get back by restoring the permission.
Nothing above can tell them apart: the ownership test rests on `images.user_id`, which is
gone with the row, so a deleted image reaches that same 403 as a foreign one. So the
distinction is made here, on the refusal path only — the happy path pays nothing for it.
A storage error propagates rather than answering either, so an unreadable database cannot
present as a deleted image and take the user's references down with it. `exists` is a bare
row probe rather than `get` for the same reason from the other side: `get` deserializes, soView on GitHub (pinned to 0b6a024f2f)
Solutions
- Log in as the image's owner or an admin user to perform the mutation
- Move the image to a Public board (as its owner) so other users gain mutation rights on it
- As the board owner, take ownership of the image's board so you qualify as the owner
- Verify the image still exists and its board record is intact; if the board was deleted, re-create/restore it
Defensive patterns
Strategy: try-catch
Validate before calling
// Before mutating, check read access/ownership if the API exposes it
const img = await fetch(`/v1/images/i/${imageName}/metadata`, {headers: authHeaders});
if (!img.ok && img.status === 403) console.warn('Skipping image (not owned):', imageName); Type guard
function canModifyImage(image, user) {
return user.is_admin || image.user_id === user.user_id || image.board_visibility === 'public';
} Try / catch
try {
await fetch(`/v1/images/i/${imageName}`, { method: 'DELETE', headers: authHeaders });
} catch (e) {
if (e.status === 403) { /* per-image skip, continue batch */ }
else if (e.status === 404) { /* drop stale reference */ }
else throw e;
} Prevention
- Only batch-modify images you created, or run the batch as an admin
- Prefer operating on boards you own rather than individual foreign images
- Handle 403 per item in batch loops instead of aborting the whole batch
- Check board visibility (Public boards grant mutation) before attempting cross-user edits
When it happens
Trigger: PATCH/DELETE on an image (e.g. /v1/images/i/{image_name}, star, update board) where current_user is not admin, not the image owner, does not own the image's board, and the board is not Public. Also fires if the image's board record is missing (BoardRecordNotFoundException falls through).
Common situations: A multiuser instance where a collaborator tries to delete or update another user's canvas image; an image whose board was deleted so ownership cannot be established; stale UI state referencing an image moved between boards.
Related errors
- Not authorized to access this image
- Not authorized to access this board
- Image not found
- Administrator account already configured
- str(e)
AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29).
Data as JSON: /api/errors/ac69aa29e1364882.
Report an issue: GitHub.