Topolo Docs Topolo Platform
Open workspace

Set workspace avatar

Choose one of twenty symbol or shape avatars, or reference a published logo in an accessible Brand kit.

Outcome

Choose one of twenty symbol or shape avatars, or reference a published logo in an accessible Brand kit.

Captures exercise real Auth/workspace handlers and canonical isolated SQL. Identity, Brand responses and the object store are simulated.

Before you start

  • Verify the pinned identity and exact app/organization/workspace from workspaces.list. Runtime authorization requires its workspace owner, organization owner/super_admin or verified platform authority; an ordinary organization admin who is not its owner is refused.

  • Resolve Brand choices through workspaces.avatar.options for this target; its Brand workspace is separate from the workspace whose avatar changes. Brand access and immutable logo publication require their own proof.

  • Retain the prior preset/Brand reference or original image before a mutation, review the exact plan and its compensation, and read canonical state after an unknown result before retrying.

  • Uploaded raster URLs are public to anyone holding the URL and permit a five-minute browser cache. Use an image suitable for public access; replacing/removing stored bytes cannot recall downloaded or cached copies.

Discover the target

topolo whoami --json
topolo actions get app_topolo_auth.workspaces.avatar.set --detail --json

After discovering the application, run topolo workspaces list <discovered-application> --json, replacing <discovered-application> with its exact published application id or slug.

Read workspaces.avatar.options with the exact workspaceId, app_id and org_id to discover accessible published Brand kit/asset choices.

Input fields

FieldRequiredMeaning
appIdYesCanonical target application id returned by discovery, such as the application that owns this workspace; it is not automatically the Auth app id.
organizationIdYesAuthorized organization id for the target workspace. The request must satisfy the active organization or verified platform authority; it cannot select an arbitrary tenant.
workspaceIdYesExisting exact-app workspace id from workspaces.list. This is the workspace whose avatar changes, not the Brand workspace used to resolve logo choices.
kindYesSelect preset or brand. Setting an uploaded image uses workspaces.avatar.upload; an image kind is not accepted by this action.
presetIdFor kind=presetOne of the twenty published symbol/shape preset identifiers in this schema. Discover the current enum rather than treating a human label as an identifier.
brandKitIdFor kind=brandExact Brand kit id from workspaces.avatar.options for this authorized workspace. Only accessible active kits with immutable published assets are eligible.
assetIdFor kind=brandExact logo or icon asset id returned together with brandKitId. A private, unpublished, retired or guessed asset is not a valid selection.

Effects

  • Writes a published preset or accessible immutable Brand logo/icon reference to the exact workspace.

  • Deletes the previous owned uploaded raster after the write; cleanup failure can follow a committed selection.

Interpret the response

Workspace management is authorized for this exact app, organization and workspace. The workspace owner can manage its own workspace. Organization owner/super_admin and verified platform authority can manage others; an ordinary organization admin who is not the workspace owner is refused. Seating in the application is separate from this management authority.

workspace is the canonical stored workspace, including its exact id/appId/organizationId, owner ids, status, slug and timestamps. avatar.kind identifies preset, image or brand; preserve every other workspace. The returned updatedAt comes from SQL and need not equal the envelope timestamp.

An uploaded image is replaced and its old stored bytes are deleted. Brand selection stores the kit/asset/public URL reference; it does not clone Brand bytes or publish a kit. A preset stores only its published preset id.

The canonical write, old-image deletion and nonfatal audit are separate. A cleanup failure can return 500 after the new avatar is stored. Read back before considering a retry or compensation. Restoring an old image requires its original bytes and produces a new opaque URL; it does not recreate the previous image id or revoke downloaded/cached copies.

Worked examples

All ids, public URLs and raster bytes below are synthetic local fixtures. Resolve authorized live targets and review the exact unchanged plan before execution. These raw target responses do not prove the live gateway, provider persistence, public rendering or cache invalidation.

Workspace owner selects one published preset

Input · payload.json:

{
  "appId": "app_topolo_web",
  "organizationId": "org_avatar_fixture",
  "workspaceId": "ws_avatar_fixture",
  "kind": "preset",
  "presetId": "leaf"
}

CLI:

topolo actions validate app_topolo_auth.workspaces.avatar.set --data @payload.json --json
topolo actions plan app_topolo_auth.workspaces.avatar.set --data @payload.json --confirm --json
topolo actions call app_topolo_auth.workspaces.avatar.set --data @payload.json --confirm --detail --json

MCP call after topolo_validate_action and topolo_plan_action on this same input:

{
  "tool": "topolo_call_action",
  "arguments": {
    "actionId": "app_topolo_auth.workspaces.avatar.set",
    "input": {
      "appId": "app_topolo_web",
      "organizationId": "org_avatar_fixture",
      "workspaceId": "ws_avatar_fixture",
      "kind": "preset",
      "presetId": "leaf"
    },
    "confirm": true,
    "detail": true
  }
}

Captured local handler response · HTTP 200:

{
  "success": true,
  "message": "Workspace avatar updated",
  "data": {
    "workspace": {
      "id": "ws_avatar_fixture",
      "appId": "app_topolo_web",
      "organizationId": "org_avatar_fixture",
      "name": "Creative",
      "avatar": {
        "kind": "preset",
        "presetId": "leaf"
      },
      "slug": "creative",
      "status": "active",
      "isDefault": false,
      "ownerUserId": "usr_avatar_owner",
      "createdByUserId": null,
      "createdAt": 1,
      "updatedAt": 1790980947,
      "archivedAt": null
    }
  },
  "timestamp": "2026-10-03T00:00:00.000Z"
}

Organization owner changes a workspace owned by another member

Input · payload.json:

{
  "appId": "app_topolo_web",
  "organizationId": "org_avatar_fixture",
  "workspaceId": "ws_avatar_fixture",
  "kind": "preset",
  "presetId": "gem"
}

CLI:

topolo actions validate app_topolo_auth.workspaces.avatar.set --data @payload.json --json
topolo actions plan app_topolo_auth.workspaces.avatar.set --data @payload.json --confirm --json
topolo actions call app_topolo_auth.workspaces.avatar.set --data @payload.json --confirm --detail --json

MCP call after topolo_validate_action and topolo_plan_action on this same input:

{
  "tool": "topolo_call_action",
  "arguments": {
    "actionId": "app_topolo_auth.workspaces.avatar.set",
    "input": {
      "appId": "app_topolo_web",
      "organizationId": "org_avatar_fixture",
      "workspaceId": "ws_avatar_fixture",
      "kind": "preset",
      "presetId": "gem"
    },
    "confirm": true,
    "detail": true
  }
}

Captured local handler response · HTTP 200:

{
  "success": true,
  "message": "Workspace avatar updated",
  "data": {
    "workspace": {
      "id": "ws_avatar_fixture",
      "appId": "app_topolo_web",
      "organizationId": "org_avatar_fixture",
      "name": "Creative",
      "avatar": {
        "kind": "preset",
        "presetId": "gem"
      },
      "slug": "creative",
      "status": "active",
      "isDefault": false,
      "ownerUserId": "usr_avatar_owner",
      "createdByUserId": null,
      "createdAt": 1,
      "updatedAt": 1790980947,
      "archivedAt": null
    }
  },
  "timestamp": "2026-10-03T00:00:00.000Z"
}

Owner saves an immutable published Brand logo reference

Input · payload.json:

{
  "appId": "app_topolo_web",
  "organizationId": "org_avatar_fixture",
  "workspaceId": "ws_avatar_fixture",
  "kind": "brand",
  "brandKitId": "kit_fixture",
  "assetId": "logo_fixture"
}

CLI:

topolo actions validate app_topolo_auth.workspaces.avatar.set --data @payload.json --json
topolo actions plan app_topolo_auth.workspaces.avatar.set --data @payload.json --confirm --json
topolo actions call app_topolo_auth.workspaces.avatar.set --data @payload.json --confirm --detail --json

MCP call after topolo_validate_action and topolo_plan_action on this same input:

{
  "tool": "topolo_call_action",
  "arguments": {
    "actionId": "app_topolo_auth.workspaces.avatar.set",
    "input": {
      "appId": "app_topolo_web",
      "organizationId": "org_avatar_fixture",
      "workspaceId": "ws_avatar_fixture",
      "kind": "brand",
      "brandKitId": "kit_fixture",
      "assetId": "logo_fixture"
    },
    "confirm": true,
    "detail": true
  }
}

Captured local handler response · HTTP 200:

{
  "success": true,
  "message": "Workspace avatar updated",
  "data": {
    "workspace": {
      "id": "ws_avatar_fixture",
      "appId": "app_topolo_web",
      "organizationId": "org_avatar_fixture",
      "name": "Creative",
      "avatar": {
        "kind": "brand",
        "brandKitId": "kit_fixture",
        "assetId": "logo_fixture",
        "url": "https://brand.topolo.dev/public/fixture-logo.png"
      },
      "slug": "creative",
      "status": "active",
      "isDefault": false,
      "ownerUserId": "usr_avatar_owner",
      "createdByUserId": null,
      "createdAt": 1,
      "updatedAt": 1790980947,
      "archivedAt": null
    }
  },
  "timestamp": "2026-10-03T00:00:00.000Z"
}

Read-back and a reviewed preset restore canonical workspace selection

Input · payload.json:

{
  "appId": "app_topolo_web",
  "organizationId": "org_avatar_fixture",
  "workspaceId": "ws_avatar_fixture",
  "kind": "preset",
  "presetId": "orbit"
}

CLI:

topolo actions validate app_topolo_auth.workspaces.avatar.set --data @payload.json --json
topolo actions plan app_topolo_auth.workspaces.avatar.set --data @payload.json --confirm --json
topolo actions call app_topolo_auth.workspaces.avatar.set --data @payload.json --confirm --detail --json

MCP call after topolo_validate_action and topolo_plan_action on this same input:

{
  "tool": "topolo_call_action",
  "arguments": {
    "actionId": "app_topolo_auth.workspaces.avatar.set",
    "input": {
      "appId": "app_topolo_web",
      "organizationId": "org_avatar_fixture",
      "workspaceId": "ws_avatar_fixture",
      "kind": "preset",
      "presetId": "orbit"
    },
    "confirm": true,
    "detail": true
  }
}

Captured local handler response · HTTP 200:

{
  "success": true,
  "message": "Workspace avatar updated",
  "data": {
    "workspace": {
      "id": "ws_avatar_fixture",
      "appId": "app_topolo_web",
      "organizationId": "org_avatar_fixture",
      "name": "Creative",
      "avatar": {
        "kind": "preset",
        "presetId": "orbit"
      },
      "slug": "creative",
      "status": "active",
      "isDefault": false,
      "ownerUserId": "usr_avatar_owner",
      "createdByUserId": null,
      "createdAt": 1,
      "updatedAt": 1790980947,
      "archivedAt": null
    }
  },
  "timestamp": "2026-10-03T00:00:00.000Z"
}

Verify the result

  • Read workspaces.list for the same app/organization and match this workspace id and requested preset or Brand kit/asset ids.

  • Verify rendering/public Brand publication independently and retain unrelated workspace avatars.

Errors and recovery

Anonymous workspace management is refused

Input:

{
  "appId": "app_topolo_web",
  "organizationId": "org_avatar_fixture",
  "workspaceId": "ws_avatar_fixture",
  "kind": "preset",
  "presetId": "leaf"
}

Captured local refusal · HTTP 401 · application/json:

{
  "success": false,
  "error": {
    "message": "Access token required",
    "code": "AUTH_ERROR",
    "details": null,
    "timestamp": "2026-10-03T00:00:00.000Z"
  }
}

Another seated member cannot change the owner workspace

Input:

{
  "appId": "app_topolo_web",
  "organizationId": "org_avatar_fixture",
  "workspaceId": "ws_avatar_fixture",
  "kind": "preset",
  "presetId": "leaf"
}

Captured local refusal · HTTP 403 · application/json:

{
  "success": false,
  "error": {
    "message": "Only the workspace owner or an organization administrator can manage this workspace",
    "code": "FORBIDDEN",
    "details": null,
    "timestamp": "2026-10-03T00:00:00.000Z"
  }
}

Foreign organization context cannot change this workspace

Input:

{
  "appId": "app_topolo_web",
  "organizationId": "org_avatar_fixture",
  "workspaceId": "ws_avatar_fixture",
  "kind": "preset",
  "presetId": "leaf"
}

Captured local refusal · HTTP 403 · application/json:

{
  "success": false,
  "error": {
    "message": "Workspaces are limited to the active organization",
    "code": "FORBIDDEN",
    "details": null,
    "timestamp": "2026-10-03T00:00:00.000Z"
  }
}

Missing exact-app workspace is not provisioned

Input:

{
  "appId": "app_topolo_web",
  "organizationId": "org_avatar_fixture",
  "workspaceId": "ws_missing",
  "kind": "preset",
  "presetId": "leaf"
}

Captured local refusal · HTTP 404 · application/json:

{
  "success": false,
  "error": {
    "message": "Workspace not found",
    "code": "NOT_FOUND",
    "details": null,
    "timestamp": "2026-10-03T00:00:00.000Z"
  }
}

Ordinary organization admin role does not manage another owner workspace

Input:

{
  "appId": "app_topolo_web",
  "organizationId": "org_avatar_fixture",
  "workspaceId": "ws_avatar_fixture",
  "kind": "preset",
  "presetId": "gem"
}

Captured local refusal · HTTP 403 · application/json:

{
  "success": false,
  "error": {
    "message": "Only the workspace owner or an organization administrator can manage this workspace",
    "code": "FORBIDDEN",
    "details": null,
    "timestamp": "2026-10-03T00:00:00.000Z"
  }
}

An inaccessible or unpublished logo cannot be guessed

Input:

{
  "appId": "app_topolo_web",
  "organizationId": "org_avatar_fixture",
  "workspaceId": "ws_avatar_fixture",
  "kind": "brand",
  "brandKitId": "kit_fixture",
  "assetId": "missing_asset"
}

Captured local refusal · HTTP 400 · application/json:

{
  "success": false,
  "error": {
    "message": "Choose a published logo from an accessible Brand workspace",
    "code": "VALIDATION_ERROR",
    "details": {
      "avatar": true
    },
    "timestamp": "2026-10-03T00:00:00.000Z"
  }
}

Old-object cleanup fails after the new preset was committed

Input:

{
  "appId": "app_topolo_web",
  "organizationId": "org_avatar_fixture",
  "workspaceId": "ws_avatar_fixture",
  "kind": "preset",
  "presetId": "leaf"
}

Captured local refusal · HTTP 500 · application/json:

{
  "success": false,
  "error": {
    "message": "Could not update workspace avatar",
    "code": "SERVER_ERROR",
    "details": null,
    "timestamp": "2026-10-03T00:00:00.000Z"
  }
}

Published recovery guidance

  • state_conflict (HTTP 409): Read the current resource state, reconcile the intended change, and retry only if still valid.

  • AUTH_ERROR (HTTP 401): Verify the pinned identity and cookie/Bearer context. Refresh only an actually expired credential; do not substitute another identity after target denial.

  • FORBIDDEN (HTTP 403): Resolve the same app/organization/workspace and its exact management authority. An ordinary seated member or organization admin is not authority over another owner’s workspace; respect refusal.

  • NOT_FOUND (HTTP 404): Rediscover the existing exact-app workspace. This action does not provision it.

  • VALIDATION_ERROR (HTTP 400): Validate the declared input. For Brand selections discover current published logo/icon choices; for uploads correct encoding, signature, MIME and decoded size.

  • SERVER_ERROR (HTTP 500): Read canonical workspace/profile state before retrying: the write may have committed before old-object cleanup. Preserve sanitized evidence and use the authorized maintainer for storage/upstream incidents; no direct storage repair is published here.

Recovery boundaries

Before writing retain the previous preset/Brand reference and any old uploaded original. After read-back, separately review this action to restore a previous preset/Brand selection, or workspaces.avatar.upload with the saved original for a previous image. Upload restoration creates a new URL and cannot recall cached/downloaded copies.

Discovery and evidence

This record belongs to Auth. Candidate human/machine URLs become valid only after native publication. Real canonical core/access migrations, identity/workspace SQL, raster validation, Brand response parsing and JSON/binary response construction were exercised. In-memory image storage, identities, seating, Brand workspace discovery and HTTP responses, ids/clock and fault injection are simulated. No live user photo, workspace avatar, Brand publication, OAuth state or Cloudflare object was changed.