I modified the name, icon and picture of an app. This is visible in the “App store”, but in other locations (under “Projects”) it has retained the old appearance.
How can I make sure it appears everywhere in the desired way?
I modified the name, icon and picture of an app. This is visible in the “App store”, but in other locations (under “Projects”) it has retained the old appearance.
How can I make sure it appears everywhere in the desired way?
We have the same issue. It should be possible to link the workspaces icon/picture/description to the app store. So we can orchestrate all UI/styling from the app store.
Now it is simply impossible and a mess as all workspaces are unlinked.
Great request!
@thomasvdl Indeed, @Johan_Tuls is correct that this is not currently possible. The app as it is added to a project is a unique instance of the app, and has it’s own image and icon. You can edit them by finding the app in a project and editing it there, but it’s currently not possible to propagate this type of thing from the app “downward” into projects. I definitely agree with you that it would make sense for that to be possible though!
but this should be doable using the REST API right?
Do you mind sharing an example on how we could do that?
@Enrique your question inspired me, because indeed, you’d think it’s possible! So, I had to give it a shot, and after quite a lot of fiddling I got it to work.
The following script expects you to set an app_id and place an image of your choosing in the folder, then it will use the REST API to update the image for the app and all the workspaces attached to this app. NB: if you had multiple images attached to your app this script currently will overwrite that with the single new image. Hope you find it useful!
import base64
import os
import requests
from dotenv import load_dotenv
from pathlib import Path
load_dotenv()
def encode_image_to_base64(image_path):
with open(image_path, "rb") as image_file:
image_data = image_file.read()
base64_encoded = base64.b64encode(image_data).decode("utf-8")
return f"{base64_encoded}"
def main():
# API token and headers to use in requests
API_TOKEN = os.getenv("API_TOKEN")
headers = {"Accept": "application/json", "Authorization": f"bearer {API_TOKEN}"}
# New image for relevant app and attached workspaces
image_path = Path(__file__).parent / "MY_NEW_IMAGE.jpg" # TODO: update with your image
base64_string = encode_image_to_base64(image_path)
# Get app and update (first) image
app_id = 2 # TODO: update to the id of your relevant app
url = f"https://demo.viktor.ai/api/apps/{app_id}/"
payload = {"images": [base64_string]}
response = requests.request("PATCH", url, headers=headers, json=payload)
print(f"Upload response for app {app_id}: {response.status_code}")
# Get workspaces for this app
url = "https://demo.viktor.ai/api/workspaces/"
workspaces = requests.request("GET", url, headers=headers, data={})
workspace_id_list = []
for ws in workspaces.json():
if ws["app"]["id"] == app_id:
workspace_id_list.append(ws["id"])
# Loop over workspaces and update image
for ws_id in workspace_id_list:
url = f"https://demo.viktor.ai/api/workspaces/{ws_id}/"
payload = {"image": base64_string}
response = requests.request("PATCH", url, headers=headers, json=payload)
print(f"Upload response for workspace {ws_id}: {response.status_code}")
if __name__ == "__main__":
main()
You are amazing @rweigand ![]()
I will try this asap ![]()
Thanks @rweigand Awesome to have this solution!
Is it also possible to set a new name and icon by using something similar to: payload = {"image": base64_string, "icon": another_base64_string, "name": "new name"} instead of payload = {"image": base64_string}?
@rweigand with this
response = requests.request("PATCH", url, headers=headers, json=payload)
print(f"Upload response for app {app_id}: {response.status_code}")
I get the result Upload response for app xx: 400 So there seems to be something wrong.
Does this request use the PAT as described in REST API | VIKTOR Documentation?
The resulting list of workspace ids from the next step seems to be alright.
Hi @thomasvdl, sure, you can also set other things besides the image. To answer your questions:
load_dotenv() in combination with a .env file that had the token. If you’re getting a 400 the authentication probably already worked though, otherwise you’d get a “not authorized” response (401)name is definitely in there, however icon is not, i’m also not sure what you mean by that?I think there might be an error in the suggested code. Using payload = {"image": base64_string} instead of payload = {"images": [base64_string]} gives a 200 response for the app. The workspaces still give a 400 response for a patch request with image with the same payload. Changing the name was successful.
The icon is wat is set here (in edit app, Dutch language version):
Well i did see i had accidentally double-pasted the code, have update the code block now. An app can have multiple images, as you can show a gallery of images in the app store, which is why i used the list. A workspace can only have a single image, which is why I only insert the one.
The icon it seems IS in the app schema ( Get App | VIKTOR Documentation ), so I think you might be able to PATCH that as well, in much the same way. Let me know if you can get that working or if you need me to try it.
Btw, here is an example of the schema of a demo app, these are all the things you can send via a PATCH for an app:
Thank you very much @rweigand! The image I used was too large
I just read the image (or the base64_string?) can be maximum 1 MB. Reducing the size solved the issue ![]()
Ah yes that’s indeed a good catch, there are restrctions for both the image as well as the icon. You mention 1 MB, and if that works it works, I don’t know the restrictions by heart but if you go through the edit modal in the VIKTOR environment the mentioned restrictions are:
So that’s less than the 1 MB you mention!?
And for the sake of complete-ness, this is the schema of a workspace that you can send via PATCH:

Thanks for the input and great that Roeland could help you with this “hack”!
Out of curiosity: do you see a situation where you do want another icon/picture for an app in a specific project or can we just always use the icon/picture from the app?
cc: @tvantil @mslootweg
Guys that for the input. I used it to make something that we wanted to have.
Each app’s first image as the image for every workspace of that app.
"""Change the image of a workspace to the first image of the app it belongs to."""
import base64
import os
import requests
from pathlib import Path
# Use your own
viktor_token = "vktrpat_XXXXXXXXXXXX" # TODO: update with your API token
base_url = "your-company.viktor.ai"
def get_response_from_viktor_api(
endpoint: str,
method: str = "GET",
data: dict = None,
) -> dict:
"""Helper function to make requests to the Viktor API.
Parameters
----------
endpoint : str
The API endpoint to call (e.g., "/api/apps/").
method : str, optional
The HTTP method to use (default is "GET").
data : dict, optional
The data to send in the request body for POST/PATCH requests.
Returns
-------
dict
The JSON response from the API as a dictionary.
Raises
------
requests.HTTPError
If the API request fails with an HTTP error.
"""
url = f"https://{base_url}{endpoint}"
headers = {
"Accept": "application/json",
"Authorization": f"Bearer {viktor_token}",
}
response = requests.request(method=method, url=url, headers=headers, json=data)
response.raise_for_status() # Raise an error for bad responses
return response.json()
def get_viktor_app_info(app_id: int) -> dict:
"""Get app information from Viktor API.
Parameters
----------
app_id : int
The ID of the app to retrieve information for.
Returns
-------
dict
A dictionary containing the app information, including name, description, images, icon etc.
Check Viktor's API documentation for the exact structure of the response.
https://docs.viktor.ai/docs/api/rest/apps-read/
"""
# Get basic app info
app_info = get_response_from_viktor_api(endpoint=f"/api/apps/{app_id}/")
# Get the app's images and add that to the basic info
images_info = get_response_from_viktor_api(endpoint=f"/api/apps/{app_id}/images/")
app_info["images"] = [image.split("base64,")[1] for image in images_info]
# let's add the app icon as well
icon_info = get_response_from_viktor_api(endpoint=f"/api/apps/{app_id}/icon/")
assert isinstance(icon_info, str)
app_info["icon"] = icon_info.split("base64,")[1]
return app_info
def get_app_workspaces(app_id: int) -> list[int]:
"""Get workspaces for a given app.
Parameters
----------
app_id : int
The ID of the app to retrieve workspaces for.
Returns
-------
list[int]
A list of workspace IDs that are associated with the given app.
"""
workspaces = get_response_from_viktor_api(endpoint="/api/workspaces/")
return [ws["id"] for ws in workspaces if ws["app"]["id"] == app_id]
def change_workspace_image(workspace_id: int, base64_image: str) -> None:
"""Change the image of a workspace.
Parameters
----------
workspace_id : int
The ID of the workspace to update.
base64_image : str
The new image encoded as a base64 string.
Returns
-------
None
Raises
------
requests.HTTPError
If the API request fails with an HTTP error.
"""
endpoint = f"/api/workspaces/{workspace_id}/"
data = {"image": base64_image}
get_response_from_viktor_api(endpoint=endpoint, method="PATCH", data=data)
def encode_image_to_base64(image_path: str) -> str:
"""Encode an image file to a base64 string.
Parameters
----------
image_path : str
The file path to the image to be encoded.
Returns
-------
str
The base64-encoded string representation of the image.
"""
with open(image_path, "rb") as image_file:
image_data = image_file.read()
base64_encoded = base64.b64encode(image_data).decode("utf-8")
return f"{base64_encoded}"
def first_image_of_app_to_workspaces(app_id: int) -> None:
"""Get the first image of an app and set it as the workspace image for all workspaces of that app.
Parameters
----------
app_id : int
The ID of the app to retrieve the first image from and update workspaces for.
Returns
-------
None
Raises
------
requests.HTTPError
If any of the API requests fail with an HTTP error.
"""
# Get app info including images
info = get_viktor_app_info(app_id=app_id)
# find which workspaces belong to the app
workspaces = get_app_workspaces(app_id=app_id)
# get the first image of the app
first_image = info['images'][0]
# Loop over workspaces and update image
for ws_id in workspaces:
change_workspace_image(workspace_id=ws_id, base64_image=first_image)
print(f"Updated workspace {ws_id} with the first image of app {app_id}")
if __name__ == "__main__":
first_image_of_app_to_workspaces(app_id=2) # TODO: update to the id of your relevant app
For us I don’t see a situation where we would like to use different pictures or icons in specific projects. But there might be a business case for instance for an independent developer who also hosts the apps and wants to sell the same app to different clients as a “custom” development, which is proven by the client’s logo.