Get in touch

Get in touch

Prefer using email? Say hi at hello@moveshelf.com

Developer

    Search Knowledge Base
Getting started
  • Setting up the environment
  • Setting up the Moveshelf API
  • Setting up a GitHub repository
  • Using the API
  • Moveshelf API documentation
  • Configuration and authentication
  • Use built-in functions
  • Examples provided by Moveshelf
  • Import
  • Create subject
  • Import subject metadata
  • Create session
  • Import session metadata
  • Create trial
  • Upload data
  • Query
  • Retrieve a subject
  • Retrieve subject metadata
  • Retrieve session
  • Retrieve session metadata
  • Retrieve trials
  • Retrieve data
  • Processing script example
  • Download data
  • Extending the Moveshelf API
  • Extending the Moveshelf API
  • knowledgebase home

    Moveshelf is designed to keep data secure but always accessible. This is true both from our web interface, for physicians and clinical operators, as well as programmatically from our API, for engineers and scientists. This chapter contains all information to get started with the Moveshelf Python API (from now on referred to as Moveshelf API). The Moveshelf API simplifies integration with Moveshelf for Python developers. It abstracts authentication, request handling, and common tasks into an intuitive, high-level interface, allowing developers to focus on building solutions without needing to manage the complexities of API interactions. The Moveshelf API enables seamless data retrieval and manipulation while integrating naturally with Python's extensive ecosystem of libraries. This makes it an ideal choice for automating workflows, processing data, and creating custom scripts that interact with Moveshelf resources. This documentation is intended for engineers, researchers, and advanced users who want to automate workflows and build custom solutions using the Moveshelf API.
    How to use this documentation
    Our goal is to break down import and querying steps into modular "puzzle pieces" that can be combined to create custom workflows. To get started, read how you can set up your programming environment, the Moveshelf API, and a GitHub repository. Then, learn how to configure the Moveshelf API and where to find a link to the complete documentation. Finally, experiment with specific examples to get started. Each example includes prerequisites that must be completed before implementation. A complete overview of this chapter's sections is available in the navigation menu on the left.



    Setting up the environment
    To interact with the Moveshelf API, you need Python and an integrated development environment (IDE) such as Visual Studio Code, PyCharm, or Spyder. This setup will allow you to run existing Python scripts from GitHub or create your own. This section walks you through setting up your environment to interact with the Moveshelf API using Python, and running a python script. While you can use any IDE that supports Python, this documentation provides instructions and examples specifically for Visual Studio Code.
    Install programs
    • Visual Studio Code
      • Install Python extension
    • Python
      • Close Visual Studio Code before installing.
      • Make sure you have selected "Add to PATH" during the install process.



    Run a Python script
    • To run a Python script, write the following command in the terminal: python <path/to/your/script.py>, and press 'Enter'


     

    Setting up the Moveshelf API
    This section walks you through the steps you need to execute the first time you want to use the Moveshelf API.
    Installation
    You can install the Moveshelf API directly via PyPI. To install the newest version available, run the following command in the terminal:
    • pip install -U moveshelf-api
    Creating an access key
    Before you can use the Moveshelf API, you need to create an access key:
    • Go to your profile page on Moveshelf (top-right corner)
    • Follow instructions to generate an API key (enter ID for the new key), and click 'Generate API Key'
    • Download the API key file and save 'mvshlf-api-key.json' in the root folder of your Git folder
    Setting up 'mvshlf-config.json'
    Additionally, you need a file called 'mvshlf-config.json', saved in the same folder as 'mvshlf-api-key.json', with the following content:
    {
    	"apiKeyFileName":"mvshlf-api-key.json",
    	"apiUrl":"",
    	"application":"None"
    }
    where the field "apiUrl" should have one of the following values:
    • EU region: "https://api.moveshelf.com/graphql"
    • US region: "https://api.us.moveshelf.com/graphql"

    Your root folder should look similar to the folder below, where your processing scripts are inside a folder, e.g., 'scripts':


     

    Setting up a GitHub repository
    While using a Git repository is not required to use the Moveshelf API, it provides easy access to public resources from Moveshelf, e.g., moveshelf-data-examples. This section explains how to clone a GitHub repository and install its dependencies.
    Clone a GitHub repository to your local machine
    • Create an account on GitHub to be able to use the public repositories
    • Open a public GitHub repository, e.g., moveshelf-data-examples and copy the repository URL


    • Open Visual Studio Code
      • Press: CTRL + Shift + P
      • Type 'Git: clone’ in the top bar
      • Press ‘Enter’
    • Paste the repository URL and press 'Enter'


  • Select a local folder where you want to clone the repository

  • Install dependencies
    To ensure your Python script runs without errors, it's best practice to list all required modules (along with their versions) in a requirements.txt file. To install these dependencies automatically:
    • Open a new terminal in Visual Studio Code


    • Run the command: pip install -r requirements.txt in the terminal and press 'Enter'
      • Make sure your terminal is in the correct folder where the requirements are saved as well.



     

    Moveshelf API documentation
    While this page covers a variety of examples of basic and advanced API usage, you can find a complete overview of the functions that are available in the Moveshelf API here.


     

    Configuration and authentication
    In this section we will explain how you can import the Moveshelf API into your processing script to securely interact with Moveshelf.
    Prerequisites
    Before implementing this example, ensure that you have performed all necessary setup steps. In particular, you should have:
    Configure the Moveshelf API
    Add the following lines of code to your processing script to use the Moveshelf API:
    import os, sys, json
    parent_folder = os.path.dirname(os.path.dirname(__file__))
    sys.path.append(parent_folder)
    from moveshelf_api.api import MoveshelfApi
    from moveshelf_api import util
    
    ## Setup the API
    # Load config
    personal_config = os.path.join(parent_folder, "mvshlf-config.json")
    if not os.path.isfile(personal_config):
        raise FileNotFoundError(
            f"Configuration file '{personal_config}' is missing.\n"
            "Ensure the file exists with the correct name and path."
        )
    
    with open(personal_config, "r") as config_file:
        data = json.load(config_file)
    
    api = MoveshelfApi(
        api_key_file=os.path.join(parent_folder, data["apiKeyFileName"]),
        api_url=data["apiUrl"],
    )


     

    Use built-in functions
    In this section we will show how you can use built-in functions of the Moveshelf API.
    Prerequisites
    Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
    Use built-in functions of the Moveshelf API
    In this example we are going to use a built-in function of the Moveshelf API to retrieve the projects the current user has access to. Add the following line of code to get a list of all the projects that are accesible by the user:
    ## Get available projects
    projects = api.getUserProjects()


     

    Examples provided by Moveshelf
    To make your life easier, we have created a list of example use cases for the Moveshelf API. Each example has a dedicated section and is explained in detail later on this page.

    Refer to the following sections for example use cases for importing data:
    Refer to the following sections for example use cases for querying data:


     

    Create subject
    This section explains how to create a new subject on Moveshelf using the Moveshelf API, based on the subject's MRN/EHR-ID. Before creating a new subject, the script first checks whether a subject with the given MRN/EHR-ID already exists on Moveshelf.
    • If a matching subject is found, it is retrieved
    • If no existing subject is found, a new subject is created and assigned the specified MRN/EHR-ID
    Prerequisites
    Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
    Implementation
    To create a new subject or retrieve an existing one, add the following lines of code to your processing script:
    ## README: this example shows how we can create a subject on Moveshelf 
    # using the Moveshelf Python API.
    # For a given project (my_project), first check if there already exists
    # a subject with a given MRN (my_subject_mrn) or name (my_subject_name). If it doesn't exist, 
    # we create a new subject with name my_subject_name, and assign my_subject_mrn if provided.
    
    ## General configuration. Set values before running the script
    my_project = "<organizationName/projectName>"  # e.g. support/demoProject
    my_subject_mrn = "<subjectMRN>"  # subject MRN, e.g. "1234567", set to None if not needed.
    my_subject_name = "<subjectName>"  # subject name, e.g. Subject1
    
    ## Get available projects
    projects = api.getUserProjects()
    
    ## Select the project
    project_names = [project["name"] for project in projects if len(projects) > 0]
    idx_my_project = project_names.index(my_project)
    my_project_id = projects[idx_my_project]["id"]
    
    ## Find the subject based on MRN or name
    subject_found = False
    if my_subject_mrn is not None:
        subject = api.getProjectSubjectByEhrId(my_subject_mrn, my_project_id)
        if subject is not None:
            subject_found = True
    if not subject_found and my_subject_name is not None:
        subjects = api.getProjectSubjects(my_project_id)
        for subject in subjects:        
            if my_subject_name == subject['name']:
                subject_found = True
                break
    if my_subject_mrn is None and my_subject_name is None:
        print("We need either subject mrn or name to be defined to be able to search for the subject.")
    
    ## Retrieve subject details if there was a match. Create new subject if there is no match
    if subject_found:
        subject_details = api.getSubjectDetails(subject["id"])
        subject_metadata = json.loads(subject_details.get("metadata", "{}"))
        print(
            f"Found subject with name: {subject_details['name']},\n"
            f"id: {subject_details['id']}, \n"
            f"and MRN: {subject_metadata.get('ehr-id', None)}"
        )
    else:
        print(
            f"Couldn't find subject with MRN: {my_subject_mrn},\n"
            f"in project: {my_project}"
        )
        new_subject = api.createSubject(my_project, my_subject_name)
        if my_subject_mrn is not None:
            subject_updated = api.updateSubjectMetadataInfo(
                new_subject["id"], json.dumps({"ehr-id": my_subject_mrn})
            )
    Validation
    To verify that the new subject has been successfully created, you can either check directly on Moveshelf or programmatically via the Moveshelf API. For the manual validation, log in to Moveshelf and navigate to the relevant project to check if the new subject appears with the correct MRN/EHR-ID. If you prefer an automated method, add the following lines of code to your processing script, right after creating the new subject, to check the subject’s details programmatically:
    # Fetch subject details using the subject ID
    new_subject_details = api.getSubjectDetails(new_subject["id"])
    new_subject_metadata = json.loads(new_subject_details.get("metadata", "{}"))
    
    # Print the subject details
    print(f"Created subject with name: {new_subject_details['name']},\n"
            f"id: {new_subject_details['id']}, \n"
            f"and MRN: {new_subject_metadata.get('ehr-id', None)}")
    This code will retrieve the newly created subject's details, including the name, ID, and MRN/EHR-ID, and print them for verification.


     

    Import subject metadata
    This example demonstrates how to import subject metadata for an existing subject on Moveshelf via the Moveshelf API. First we retrieve the subject from Moveshelf via its MRN/EHR-ID. Then, we update its subject metadata.

    Important: The keys in the metadata dictionary to be imported must match the metadata template that is configured for the Moveshelf project you want to import to. You can download the configured metadata template from the project overview.
    Prerequisites
    Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
    Implementation
    To import subject metadata to an existing subject, add the following lines of code to your processing script:
    ## README: this example shows how we can import subject metadata to an existing
    # subject on Moveshelf using the Moveshelf Python API.
    # This code assumes you have implemented the 'Create subject' example, and
    # that you have found the subject with a given EHR-id/MRN (my_subject_mrn)
    # or name (my_subject_name) within a given project (my_project), and that 
    # you have access to the subject ID
    # For that subject, update subject metadata (my_subject_metadata)
    
    ## General configuration. Set values before running the script
    my_project = "<organizationName/projectName>"  # e.g. support/demoProject
    my_subject_mrn = "<subjectMRN>"  # subject MRN, e.g. "1234567" or None
    my_subject_name = "<subjectName>"  # subject name, e.g. Subject1
    
    # Manually define the subject metadata dictionary you would like to import
    my_subject_metadata = {
        "subject-first-name": "<subjectFirstName>",
        "subject-middle-name": "<subjectMiddleName>",
        "subject-last-name": "<subjectLastName>",
        "ehr-id": my_subject_mrn,
        "interventions": [ # List of interventions
            {
                "id": 0, # <idInterv1>
                "date": "<dateInterv1>", # "YYYY-MM-DD" format
                "site": "<siteInterv1>",
                "surgeon": "<surgeonInterv1>",
                "procedures": [
                    {
                        "side": "<sideProc1Interv1>",
                        "location": "<locationProc1Interv1>",
                        "procedure": "<procedureProc1Interv1>",
                        "location-modifier": "<locationModProc1Interv1>",
                        "procedure-modifier": "<procedureModProc1Interv1>"
                    },
                    {
                        "side": "<sideProc2Interv1>",
                        # ... You can add multiple procedures
                    }
                ],
                "surgery-dictation": "<surgeryDictationInterv1>"
            },
            # ... Additional interventions can be added here, each represented as a dictionary
            # Increment the "id" for each new intervention (e.g., 1, 2, 3,...)
            # Ensure each intervention contains a complete set of fields
        ]
        # ... Add additional fields you would like to import
    }
    
    ## Add here the code to retrieve the project and find an existing subject and its "subject_details"
    # ... subject_found = True
    
    # Import subject metadata
    subject_updated = api.updateSubjectMetadataInfo(
        subject["id"], json.dumps(my_subject_metadata)
    )
     

    It is also possible to load subject metadata from a JSON file stored on your local machine, e.g., 'moveshelf_config_import.json'. Instead of defining my_subject_mrn and my_subject_metadata manually, you can load them from a JSON file located in your root folder as shown below:
    # Define the path to the local JSON file
    local_metadata_json = os.path.join(parent_folder, "moveshelf_config_import.json")
    
    # Load JSON file
    with open(local_metadata_json, "r") as file:
        local_metadata = json.load(file)
    
    # Extract subjectMetadata dictionary from metadata JSON
    my_subject_metadata = local_metadata.get("subjectMetadata", {})
    my_subject_mrn = my_subject_metadata.get("ehr-id", "")
    
    # Extract interventionMetadata list from metadata JSON
    my_subject_metadata["interventions"] = local_metadata.get("interventionMetadata", [])
    Validation
    To verify that the subject metadata has been successfully imported, you can either check directly on Moveshelf or programmatically via the Moveshelf API. For the manual validation, log in to Moveshelf and navigate to the relevant project to check if the subject appears with the correct subject metadata. If you prefer an automated method, add the following lines of code to your processing script, right after updating subject metadata, to check the subject’s metadata programmatically:
    # Fetch subject details using the subject ID
    subject_details = api.getSubjectDetails(subject["id"])
    subject_metadata = json.loads(subject_details.get("metadata", "{}"))
    
    # Print the subject details
    print(f"Created subject with name: {subject_details['name']},\n"
            f"id: {subject_details['id']}, \n"
            f"and metadata: {subject_metadata}") 
    This code will retrieve the subject's details, including the name, ID, and subject metadata, and print them for verification.


     

    Create session
    This section explains how to create a new session for a subject with a specified MRN on Moveshelf using the Moveshelf API, based on the session's name. Before creating a new session, the script first checks whether a session with the specified name already exists for that subject on Moveshelf.
    • If a matching session is found, it is retrieved
    • If no existing session is found, a new session is created and assigned the name and date specified in my_session
    Prerequisites
    Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
    Implementation
    To create a new session or retrieve an existing one, add the following lines of code to your processing script:
    ## README: this example shows how we can create a session on Moveshelf 
    # using the Moveshelf API.
    # This code assumes you have implemented the 'Create subject' example, and
    # that you have found the subject with a given EHR-id/MRN (my_subject_mrn)
    # or subject name (my_subject_name) within a given project (my_project),
    # and that you have access to the subject ID
    # For that subject, we check if a session with the specified name 
    # (my_session) exists. If it doesn't exist, a new session is created with 
    # the name and date specified in my_session (where the date serves as both
    # the session's name and session's date).
    
    
    ## General configuration. Set values before running the script
    my_project = "<organizationName/projectName>"  # e.g. support/demoProject
    my_subject_mrn = "<subjectMRN>"  # subject MRN, e.g. "1234567" or None
    my_subject_name = "<subjectName>"  # subject name, e.g. Subject1
    my_session = "YYYY-MM-DD" # session name, e.g. 2025-09-05
    
    ## Add here the code to retrieve the project and find an existing subject and its "subject_details"
    # ... subject_found = True
    
    ## Find existing session
    sessions = subject_details.get('sessions', [])
    session_found = False
    for session in sessions:
        try:
            session_name = session['projectPath'].split('/')[2]
        except:
            session_name = ""
        if session_name == my_session:
            session_id = session['id']
            session = api.getSessionById(session_id) # get all required info for that session
            session_found = True
            print(f"Session {my_session} already exists")
            break
    
    ## Create new session if no match was found
    if not session_found:
        session_path = "/" + subject_details['name'] + "/" + my_session + "/"
        session = api.createSession(my_project, session_path, subject_details['id']) # Tries to
        # extract and set the session date from session_path
        session_id = session['id']
           

    Note: it is also possible to explicitly set the session date instead of extracting it from the session name my_session. To do so, provide an optional fourth parameter, e.g., my_session_date (in "YYYY-MM-DD" format), to createSession, i.e.,
    session = api.createSession(my_project, session_path, subject_details['id'], my_session_date)
    In this way it is possible to have a session name that is not a date and different from my_session_date.
    This session date can be extracted from a JSON file stored on your local machine, e.g., 'moveshelf_config_import.json'. The JSON file should have the key "sessionDate" that stores the session date in "YYYY-MM-DD" format. The key "sessionDate" must be at the same level as keys "subjectMetadata" and "sessionMetadata". You can then extract the session date using:
    my_session_date = local_metadata.get("sessionDate", None)
    where local_metadata is the loaded JSON file.
    Validation
    To verify that the new session has been successfully created, you can either check directly on Moveshelf or programmatically via the Moveshelf API. For the manual validation, log in to Moveshelf and navigate to the relevant project and subject to check if the new session appears with the correct name. If you prefer an automated method, add the following lines of code to your processing script, right after creating the new session, to check the session’s details programmatically:
    # Fetch session details using the session ID
    new_session = api.getSessionById(session_id)
    print(
        f"Found session with projectPath: {new_session['projectPath']},\n"
        f"and id: {new_session['id']}"
    )
    This code will retrieve the newly created session's details, including the project path (that includes the subject name and the session name), and ID, and print them for verification.


     

    Import session metadata
    This example demonstrates how to import session metadata for an existing session on Moveshelf via the Moveshelf API. First we retrieve the subject from Moveshelf via its MRN/EHR-ID. Then, we retrieve the session via its session date, and update its metadata.

    Important: The keys in the metadata dictionary to be imported must match the metadata template that is configured for the Moveshelf project you want to import to. You can download the configured metadata template from the project overview.
    Prerequisites
    Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
    Implementation
    To import session metadata to an existing subject and session, add the following lines of code to your processing script:
    ## README: this example shows how we can import session metadata to an existing
    # session on Moveshelf using the Moveshelf Python API.
    # This code assumes you have implemented the 'Create session' example, and
    # that you have found the session (my_session) for a subject with a given
    # EHR-id/MRN (my_subject_mrn) or name (my_subject_name) within a given 
    # project (my_project), and that you have access to the session ID and session_name
    # For that session, update session metadata (my_session_metadata)
    
    ## General configuration. Set values before running the script
    my_project = "<organizationName/projectName>"  # e.g. support/demoProject
    my_subject_mrn = "<subjectMRN>"  # subject MRN, e.g. "1234567" or None
    my_subject_name = "<subjectName>"  # subject name, e.g. Subject1
    my_session = "<sessionDate>" # "YYYY-MM-DD" format
    
    # Manually define the session metadata dictionary you would like to import
    my_session_metadata = {
        "interview-fms5m": "1",
        "vicon-leg-length": [
                {
                    "value": "507",
                    "context": "left"
                },
                {
                    "value": "510",
                    "context": "right"
                }
            ],
        # ... Add additional fields you would like to import
    }
    
    ## Add here the code to retrieve the project and find an existing subject 
    ## and existing session
    # loop over sessions until we find session_found = True
    
    session_id = session["id"]
    session = api.getSessionById(session_id)    # get all required info for that session
    updated_session = api.updateSessionMetadataInfo(
        session_id,
        session_name,
        json.dumps({"metadata": my_session_metadata})
    )
    print(f"Updated session {session_name}: {updated_session}")

    It is also possible to load session metadata from a JSON file stored on your local machine, e.g., 'moveshelf_config_import.json'. Instead of defining my_subject_mrn and my_session_metadata manually, you can load them from a JSON file located in your root folder as shown below:
    # Define the path to the local JSON file
    local_metadata_json = os.path.join(parent_folder, "moveshelf_config_import.json")
    
    # Load JSON file
    with open(local_metadata_json, "r") as file:
        local_metadata = json.load(file)
    
    # Extract subjectMetadata dictionary from metadata JSON
    my_subject_metadata = local_metadata.get("subjectMetadata", {})
    my_subject_mrn = my_subject_metadata.get("ehr-id", "")
    
    # Extract sessionMetadata dictionary from metadata JSON
    my_session_metadata = local_metadata.get("sessionMetadata", {})
    Validation
    To verify that the session metadata has been successfully imported, you can either check directly on Moveshelf or programmatically via the Moveshelf API. For the manual validation, log in to Moveshelf and navigate to the relevant project to check if the session appears with the correct session metadata. If you prefer an automated method, add the following lines of code to your processing script, right after updating session metadata, to check the session’s metadata programmatically:
    # Fetch session details using the session ID
    updated_session_details = api.getSessionById(session_id)
    updated_session_metadata = updated_session_details.get("metadata", None)
    
    # Print updated session metadata
    print(
        f"Updated session with projectPath: {session['projectPath']},\n"
        f"id: {session_id}.\n"
        f"New metadata: {updated_session_metadata}"
    )
    This code will retrieve the updated session's details, including the projectPath, ID, and session metadata, and print them for verification.


     

    Create trial
    This section explains how to create a new trial in a condition of a session for a subject with a specified MRN on Moveshelf using the Moveshelf API. Before creating a new trial, the script first checks whether a trial with the specified name already exists within the condition on Moveshelf.
    • If a matching trial in that condition is found, it is retrieved
    • If no existing trial with that name in the condition is found, a new trial is created
    Prerequisites
    Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
    Implementation
    To create a new trial or retrieve an existing one, add the following lines of code to your processing script:
    ## README: this example shows how we can create a trial on Moveshelf 
    # using the Moveshelf API.
    # This code assumes you have implemented the 'Create subject' example, 
    # that you have found the subject with a given EHR-id/MRN (my_subject_mrn)
    # or name (my_subject_name) within a given project (my_project), and that you have 
    # access to the subject ID
    # This code also assumes you have implemented the 'Create session' example, 
    # and that you have found the session with a specific name (my_session)
    
    # For that subject and session, to understand if we need to create a new trial or find an 
    # existing trial, we first check if a condition with the specified name (my_condition) 
    # exists. If it doesn't exist yet, or if a trial with a specific name (my_trial) is not found 
    # in the existing condition, we create a new trial. Otherwise, we use the exising trial.
    
    
    ## General configuration. Set values before running the script
    my_project = "<organizationName/projectName>"  # e.g. support/demoProject
    my_subject_mrn = "<subjectMRN>"  # subject MRN, e.g. "1234567" or None
    my_subject_name = "<subjectName>"  # subject name, e.g. Subject1
    my_session = "YYYY-MM-DD" # session name, e.g. 2025-09-05
    my_condition = "<conditionName>"  # e.g. "Barefoot"
    my_trial = "<trialName>" # when set to None, we increment the trial number starting with "Trial-1"
    
    ## Add here the code to retrieve the project and find an existing subject and its "subject_details"
    # ... subject_found = True
    
    ## Add here the code to retrieve an existing session and get its details using "getSessionById"
    
    # Get conditions in the session
    conditions = []
    conditions = util.getConditionsFromSession(session, conditions)
    
    condition_exists = any(c["path"].replace("/", "") == my_condition for c in conditions)
    condition = next(c for c in conditions if c["path"].replace("/", "") == my_condition) \
        if condition_exists else {"path": my_condition, "clips": []}
    
    clip_id = util.addOrGetTrial(api, session, condition, my_trial)
    print(f"Clip created with id: {clip_id}")

    It is also possible to load the condition names and list of trials from a JSON file stored on your local machine, e.g., 'moveshelf_config_import.json'. Instead of defining my_condition and my_trial manually, you can access them from a JSON file located in your root folder as shown below:
    # Define the path to the local JSON file
    local_metadata_json = os.path.join(parent_folder, "moveshelf_config_import.json")
    
    # Load JSON file
    with open(local_metadata_json, "r") as file:
        local_metadata = json.load(file)
    
    # Extract conditionDefinition from metadata JSON
    my_condition_definition = local_metadata.get("conditionDefinition", {})
    
    ## Add here the code to retrieve the project and find an existing subject and its "subject_details"
    # ... subject_found = True
    
    ## Add here the code to retrieve an existing session and get its details using "getSessionById"
    
    # Get conditions in the session
    conditions = []
    conditions = util.getConditionsFromSession(session, conditions)
    
    # loop over conditions defined in conditionDefinition
    for my_condition, my_trials in my_condition_definition.items():
    
        condition_exists = any(c["path"].replace("/", "") == my_condition for c in conditions)
        condition = next(c for c in conditions if c["path"].replace("/", "") == my_condition) \
            if condition_exists else {"path": my_condition, "clips": []}
        
        # Loop over trials list defined for my_condition
        for my_trial in my_trials:
            clip_id = util.addOrGetTrial(api, session, condition, my_trial)
            print(f"Clip created with id: {clip_id}") 
    Validation
    To verify that the new trial has been successfully created, you can either check directly on Moveshelf or programmatically via the Moveshelf API. For the manual validation, log in to Moveshelf and navigate to the relevant project, subject and session to check if the new trial appears with the correct name. If you prefer an automated method, add the following lines of code to your processing script, right after creating the new trial, to check the trial details programmatically:
    # Fetch trial using the trial ID
    new_clip = api.getClipData(clip_id)
    print(
        f"Found a clip with title: {new_clip['title']},\n"
        f"and id: {new_clip['id']}"
    ) 
    This code will retrieve the newly created trial details, including the title and ID, and print them for verification.


     

    Upload data
    This section explains how to upload data into a specific trial in a condition of a session for a subject with a specified MRN on Moveshelf using the Moveshelf API. Before uploading, the script first checks whether data file with the same name already exists within the trial on Moveshelf and will skip the upload if it's already present. Please refer to this section for supported data types.
    Prerequisites
    Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
    Implementation
    To upload data into the trial identified by the "clip_id", add the following lines of code to your processing script:
    ## README: this example shows how we can upload date into a trial on 
    # Moveshelf using the Moveshelf API.
    # This code assumes you have implemented the 'Create subject' example, 
    # that you have found the subject with a given EHR-id/MRN (my_subject_mrn)
    # or name (my_subject_name) within a given project (my_project), and that 
    # you have access to the subject ID.
    # This code also assumes you have implemented the 'Create session' example, 
    # and that you have found the session with a specific name (my_session)
    # This code also assumes you have implemented the 'Create trial' example, 
    # and that you have found the trial with a specific name (my_trial).
    
    # For that trial as part of a subject and session, we need the "clip_id". 
    
    ## General configuration. Set values before running the script
    my_project = "<organizationName/projectName>"  # e.g. support/demoProject
    my_subject_mrn = "<subjectMRN>"  # subject MRN, e.g. "1234567" or None
    my_subject_name = "<subjectName>"  # subject name, e.g. Subject1
    my_session = "YYYY-MM-DD" # session name, e.g. 2025-09-05
    my_condition = "<conditionName>"  # e.g. "Barefoot"
    my_trial = "<trialName>"
    filter_by_extension = None
    data_file_name = None  # with None, the name of the actual file is used, if needed this name can be used
    files_to_upload = [<pathToFile_1>, <pathToFile2>,...] # list of files to be added to the trial
    data_type = "raw"
    
    ## Add here the code to retrieve the project and find an existing subject and its "subject_details"
    # ... subject_found = True
    
    ## Add here the code to retrieve an existing session and get its details using "getSessionById"
    
    ## Add here the code to retrieve an existing trial and get the ID: clip_id
    
    existing_additional_data = api.getAdditionalData(clip_id)
    existing_additional_data = [
        data for data in existing_additional_data if not filter_by_extension
        or os.path.splitext(data["originalFileName"])[1].lower() == filter_by_extension]
    existing_file_names = [data["originalFileName"] for data in existing_additional_data if
        len(existing_additional_data) > 0]
    
    ## Upload data
    for file_to_upload in files_to_upload:
        file_name = data_file_name if data_file_name is not None else os.path.basename(file_to_upload)
        if file_name in existing_file_names:
            print(file_name + " was found in clip, will skip this data.")
            continue
    
        print("Uploading data for : " + my_condition + ", " + my_trial + ": " + file_name)
    
        dataId = api.uploadAdditionalData(file_to_upload, clip_id, data_type, file_name)    

    It is also possible to upload trial data and metadata in batch using information from a JSON file stored on your local machine, e.g., 'moveshelf_config_import.json'. Instead of defining a list of files_to_upload, define the path to the folder containing session data as shown below:
    ## General configuration. Set values before running the script
    session_folder_path = "<pathToDataFolder>"
    file_extension_to_upload = "<extension>" # e.g., ".GCD"
    
    # Define the path to the local JSON file
    local_metadata_json = os.path.join(parent_folder, "moveshelf_config_import.json")
    
    # Load JSON file
    with open(local_metadata_json, "r") as file:
        local_metadata = json.load(file)
    
    # Extract conditionDefinition, representativeTrials, and trialSideSelection
    # from metadata JSON
    my_condition_definition = local_metadata.get("conditionDefinition", {})
    my_representative_trials = local_metadata.get("representativeTrials", {})
    my_trial_side_selection = local_metadata.get("trialSideSelection", {})
    
    ## Add here the code to retrieve the project and find an existing subject and its "subject_details"
    # ... subject_found = True
    
    ## Add here the code to retrieve an existing session and get its details using "getSessionById"
    
    ## Add here the code to get existing conditions and loop over conditions and trials 
    # ... clip_id = util.addOrGetTrial(api, session, condition, my_trial)
    
    ## Upload data
    file_name = data_file_name if data_file_name is not None else f"{my_trial}{file_extension_to_upload}"
    print("Uploading data for : " + my_condition + ", " + my_trial + ": " + file_name)
    
    dataId = api.uploadAdditionalData(
        os.path.join(session_folder_path, f"{my_trial}{file_extension_to_upload}"),
        clip_id,
        data_type,
        file_name
    ) 
    
    metadata_dict = {
        "title": my_trial
    }
    
    # Initialize the trial template
    custom_options_dict = {"trialTemplate": {}}
    trial_template = custom_options_dict["trialTemplate"]
    
    # Add side information if available
    side_value = my_trial_side_selection.get(my_trial)
    if side_value:
        trial_template["sideAdditionalData"] = {dataId: side_value}
    
    # Handle representative trial flags
    representative_trial = my_representative_trials.get(my_trial, "")
    
    rep_flags = {}
    if "left" in representative_trial:
        rep_flags["left"] = True
    if "right" in representative_trial:
        rep_flags["right"] = True
    if rep_flags:
        trial_template["representativeTrial"] = rep_flags
    
    # Remove the trialTemplate key if it's still empty
    if not trial_template:
        custom_options_dict = {}
    
    # Convert to JSON string
    custom_options = json.dumps(custom_options_dict)
    
    api.updateClipMetadata(clip_id, metadata_dict, custom_options)
    
    # Fetch trial using the trial ID
    new_clip = api.getClipData(clip_id)
    print(
        f"Found a clip with title: {new_clip['title']},\n"
        f"id: {new_clip['id']},\n"
        f"and custom options: {new_clip['customOptions']}"
    )
            
    PDF/image files
    PDF files and image files (e.g. ".jpg" or ".png") can be stored on Movesehelf within a trial, but for this we have a dedicated place on Moveshelf, so the files are picked up e.g. when exporting to a Word document. The PDF/image files are stored in a condition called "Additional files" with a separate trial per PDF/image and a name that is typically the same as the PDF/image file name in that trial. To upload, the code as shown above can be used, with the following modifications:
    ## Modifications needed to upload PDF/image files into "Additional files"
    my_condition = "Additional files"
    my_trial = "<pdf_file_name>" 
    filter_by_extension = ".pdf"  # for  images, use e.g. ".jpg"/".png" if needed.
    files_to_upload = [
        "<path_to_additional_data.pdf>",
    ]
    data_type = "doc"  # for images, set data_type to "img"
    Raw motion data - ZIP
    For raw motion data provided in a ZIP archive, we suggest to use the same "Additional files" condition that is also used for PDF/image files. Specifically, for ZIP files with raw motion data we suggest to use a trial named "Raw motion data". The additional data file can then be named e.g. "Raw motion data - my_session.zip". To upload, the code as shown above can be used, with the following modifications:
    ## Modifications needed to upload raw motion data in ZIP archive into "Additional files"
    my_condition = "Additional files"
    my_trial = "Raw motion data"
    data_file_name = "Raw motion data - " + my_session + ".zip"  # Set to None for actual file name.
    filter_by_extension = ".zip"  # set to None if used without filtering
    files_to_upload = [
        "<path_to_additional_data.zip>",
    ]
    data_type = "raw"


     

    Retrieve a subject
    This section explains how to retrieve a subject from Moveshelf using the Moveshelf API, based on the subject's MRN/EHR-ID.
    • If a matching subject is found, it is retrieved
    • If no matching subject is found, a message is displayed
    Prerequisites
    Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
    Implementation
    To retrieve an existing subject, add the following lines of code to your processing script:
    ## README: this example shows how we can retrieve a subject from Moveshelf 
    # using the Moveshelf API.
    # For a given project (my_project), retrieve a subject based on either the given MRN (my_subject_mrn) 
    # or the subject name (my_subject_name)
    
    ## General configuration. Set values before running the script
    my_project = "<organizationName/projectName>"  # e.g. support/demoProject
    my_subject_mrn = "<subjectMRN>"  # subject MRN, e.g. "1234567", set to None if you want to use the name
    my_subject_name = "<subjectName>"  # subject name, set to None if you want to use the MRN
    
    ## Get available projects
    projects = api.getUserProjects()
    
    ## Select the project
    project_names = [project["name"] for project in projects if len(projects) > 0]
    idx_my_project = project_names.index(my_project)
    my_project_id = projects[idx_my_project]["id"]
    
    ## Find the subject based on MRN or name
    subject_found = False
    if my_subject_mrn is not None:
        subject = api.getProjectSubjectByEhrId(my_subject_mrn, my_project_id)
        if subject is not None:
            subject_found = True
    if not subject_found and my_subject_name is not None:
        subjects = api.getProjectSubjects(my_project_id)
        for subject in subjects:        
            if my_subject_name == subject['name']:
                subject_found = True
                break
    if my_subject_mrn is None and my_subject_name is None:
        print("We need either subject mrn or name to be defined to be able to search for the subject.")
    
    ## Print message
    if subject_found:
        subject_details = api.getSubjectDetails(subject["id"])
        subject_metadata = json.loads(subject_details.get("metadata", "{}"))
        print(
            f"Found subject with name: {subject_details['name']},\n"
            f"id: {subject_details['id']}, \n"
            f"and MRN: {subject_metadata.get('ehr-id', None)}"
        )
    else:
        print(
            f"Couldn't find subject with MRN: {my_subject_mrn},\n"
            f"in project: {my_project}"
        )


     

    Retrieve subject metadata
    This example demonstrates how to retrieve subject metadata for an existing subject on Moveshelf via the Moveshelf API.
    Prerequisites
    Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
    Implementation
    To retrieve subject metadata from an existing subject, add the following lines of code to your processing script:
    ## README: this example shows how we can retrieve subject metadata from an existing subject on 
    # Moveshelf using the Moveshelf Python API.
    # This code assumes you have implemented the 'Retrieve subject' example, and that you have found 
    # the subject with a given EHR-id/MRN (my_subject_mrn) or name (my_subject_name) within a given 
    # project (my_project), that you have access to the subject ID and obtained the "subject_details"
    
    
    ## General configuration. Set values before running the script
    my_project = "<organizationName/projectName>"  # e.g. support/demoProject
    my_subject_mrn = "<subjectMRN>"  # subject MRN, e.g. "1234567" or None
    my_subject_name = "<subjectName>"  # subject name, e.g. Subject1 or None
    
    ## Add here the code to retrieve the project and get the "subject_details" for that subject
    
    subject_metadata = json.loads(subject_details.get("metadata", "{}"))
    print(
        f"Found subject with name: {subject_details['name']},\n"
        f"id: {subject_details['id']}, \n"
        f"and metadata: {subject_metadata}"
    ) 
             


     

    Retrieve session
    This section explains how to retrieve a session with a specific date for a subject with a specified MRN on Moveshelf using the Moveshelf API.
    Prerequisites
    Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
    Implementation
    To retrieve a session with a specific date from a subject, add the following lines of code to your processing script:
    ## README: this example shows how we can retrieve sessions from Moveshelf 
    # using the Moveshelf API.
    # This code assumes you have implemented the 'Retrieve subject' example, and
    # that you have found the subject with a given EHR-id/MRN (my_subject_mrn)
    # or name (my_subject_name) within a given project (my_project), and that you 
    # have access to the subject ID. 
    # Then, for that subject, retrieve the specified session (my_session).
    
    
    ## General configuration. Set values before running the script
    my_project = "<organizationName/projectName>"  # e.g. support/demoProject
    my_subject_mrn = "<subjectMRN>"  # subject MRN, e.g. "1234567" or None
    my_subject_name = "<subjectName>"  # subject name, e.g. Subject1 or None
    my_session = "<sessionDate>" # "YYYY-MM-DD" format
    
    ## Add here the code to retrieve the project and find an existing subject and its "subject_details"
    # ... subject_found = True
    
    ## Get sessions
    sessions = subject_details.get("sessions", [])
    
    # Loop over sessions
    session_found = False
    for session in sessions:
        try:
            session_name = session["projectPath"].split("/")[2]
        except:
            session_name = ""
        if session_name == my_session:
            session_found = True
            session_id = session["id"]
            print(
                f"Found session with projectPath: {session['projectPath']},\n"
                f"and id: {session_id}"
            )
            break
    
    if not session_found:
        print(
            f"Couldn't find session: {my_session},\n"
            f"for subject with MRN: {my_subject_mrn}"
        ) 
           


     

    Retrieve session metadata
    This section explains how to retrieve session metadata for a specified session for a subject with a specified MRN on Moveshelf using the Moveshelf API.
    Prerequisites
    Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
    Implementation
    To retrieve session metadata from a specific session and subject, add the following lines of code to your processing script:
    ## README: this example shows how we can retrieve session metadata from Moveshelf 
    # using the Moveshelf API.
    # This code assumes you have implemented the 'Retrieve sessopm' example, and
    # that you have found session (my_session) for the subject with a given EHR-id/MRN 
    # (my_subject_mrn) or name (my_subject_name) within a given project (my_project), and 
    # that you have access to the session ID.
    
    ## General configuration. Set values before running the script
    my_project = "<organizationName/projectName>"  # e.g. support/demoProject
    my_subject_mrn = "<subjectMRN>"  # subject MRN, e.g. "1234567" or None
    my_subject_name = "<subjectName>"  # subject name, e.g. Subject1 or None
    my_session = "<sessionDate>" # "YYYY-MM-DD" format
    
    ## Add here the code to retrieve the project and find an existing subject and session using "getSessionById"
    
    session_id = session["id"]
    session_metadata = session.get("metadata", None)
    print(
        f"Found session with projectPath: {session['projectPath']},\n"
        f"id: {session_id},\n"
        f"and metadata: {session_metadata}"
    )
           


     

    Retrieve trials
    This section explains how to retrieve trials within a specific condition of a specific session for a subject with a specified MRN on Moveshelf using the Moveshelf API.
    Prerequisites
    Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
    Implementation
    To retrieve the trials within a specific condition, add the following lines of code to your processing script:
    ## README: this example shows how we can retrieve trials from Moveshelf 
    # using the Moveshelf API.
    # This code assumes you have implemented the 'Retrieve subject' example, 
    # that you have found the subject with a given EHR-id/MRN (my_subject_mrn)
    # or name (my_subject_name) within a given project (my_project), that you 
    # have access to the subject ID, and you have implemented the 
    # 'Retrieve session' example to retrieve the specified session (my_session).
    
    
    ## General configuration. Set values before running the script
    my_project = "<organizationName/projectName>"  # e.g. support/demoProject
    my_subject_mrn = "<subjectMRN>"  # subject MRN, e.g. "1234567" or None
    my_subject_name = "<subjectName>"  # subject name, e.g. Subject1 or None
    my_session = "<sessionDate>" # "YYYY-MM-DD" format
    my_condition = "<conditionName>"  # e.g. "Barefoot"
    
    ## Add here the code to retrieve the project and find an existing subject and its "subject_details"
    # ... subject_found = True
    
    ## Add here the code to retrieve an existing session and get its details using "getSessionById"
    
    # Get conditions in the session
    conditions = []
    conditions = util.getConditionsFromSession(session, conditions)
    
    condition_exists = any(c["path"].replace("/", "") == my_condition for c in conditions)
    condition = next(c for c in conditions if c["path"].replace("/", "") == my_condition) \
        if condition_exists else {"path": my_condition, "clips": []}
    trial_count = len(condition["clips"]) if condition_exists else 0
    clips_in_condition = [clip for clip in condition["clips"]] if condition_exists and trial_count > 0 else []
    
    # get the id and title of each clip
    for clip in clips_in_condition:
        clip_id = clip["id"]
        clip_title = clip["title"]
    
        print(
            f"Found a clip with title: {clip_title},\n"
            f"and id: {clip_id}"
        ) 
           


     

    Retrieve data
    This section explains how to retrieve additional data (files) within a specific trial of a condition within a session of a subject (specified by MRN) on Moveshelf using the Moveshelf API.
    Prerequisites
    Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
    Implementation
    To retrieve the additional data files with that trial, add the following lines of code to your processing script:
    ## README: this example shows how we can retrieve additional data from 
    # Moveshelf using the Moveshelf API.
    # This code assumes you have implemented the 'Retrieve subject' example, 
    # that you have found the subject with a given EHR-id/MRN (my_subject_mrn)
    # or name (my_subject_name) within a given project (my_project), that you 
    # have access to the subject ID, you have implemented the 'Retrieve session' 
    # example, and you have implemented the 'Retrieve trial' example to get or 
    # create the specified trial (my_trial) within a specific condition (my_condition).
    
    ## Import necessary modules
    import requests # Used to send HTTP requests to retrieve additional data files from Moveshelf
    
    ## General configuration. Set values before running the script
    my_project = "<organizationName/projectName>"  # e.g. support/demoProject
    my_subject_mrn = "<subjectMRN>"  # subject MRN, e.g. "1234567" or None
    my_subject_name = "<subjectName>"  # subject name, e.g. Subject1 or None
    my_session = "<sessionDate>" # "YYYY-MM-DD" format
    my_condition = "<conditionName>"  # e.g. "Barefoot"
    my_trial = "<trialName>"
    filter_by_extension = None
    
    ## Add here the code to retrieve the project and find an existing subject and its "subject_details"
    # ... subject_found = True
    
    ## Add here the code to retrieve an existing session and get its details using "getSessionById"
    
    ## Add here the code to retrieve an existing trial and get the ID: clip_id
    
    # get all additional data
    existing_additional_data = api.getAdditionalData(clip_id)
    # if filter_by_extension is provided, only get the data with that extension
    existing_additional_data = [
        data for data in existing_additional_data if not filter_by_extension
        or os.path.splitext(data["originalFileName"])[1].lower() == filter_by_extension]
    existing_file_names = [data["originalFileName"] for data in existing_additional_data if 
        len(existing_additional_data) > 0]
    
    print("Existing data for clip: ")
    print(*existing_file_names, sep = "\n")
    
    # Loop through the data found, and download if the "uploadStatus" is set to "Complete"
    # The data will be available in "file_data", from which it can e.g. be used to 
    # process, or written to a file on local storage
    for data in existing_additional_data:
        if data["uploadStatus"] == "Complete":
            file_data = requests.get(data["originalDataDownloadUri"]).content
           
    PDF/image files
    PDF files and image files (e.g. ".jpg" or ".png") can be stored as part of a trial and retrieved following the example before, but on Moveshelf we have a dedicated place for PDF/image files, so these are picked up e.g. when exporting to a Word document. For this, PDF/image files are stored in a condition called "Additional files" with a trial per PDF/image and a name that is typically the same as the PDF/image file name in that trial. To retrieve, the code as shown above can be used, with the following modifications:
    ## Modifications needed to extract PDF/image files stored within "Additional files"
    my_condition = "Additional files"
    filter_by_extension = ".pdf"  # to be modified based on the required extension
           
    Raw motion data - ZIP
    For raw motion data provided in a ZIP archive, we suggest to use the same "Additional files" condition that is also used for PDF/image files. Specifically, for ZIP files with raw motion data we suggest to use a trial named "Raw motion data". The code to retrieve the data from this trial is similar to the PDF/image example above, but this time specify the specific trial name my_trial you first search for the specific trial:
    ## Modifications needed to extract ZIP files stored within "Additional files"
    my_condition = "Additional files"
    my_trial = "Raw motion data"        # To be modified based on the exact trial that is required.
    
    filter_by_extension = ".zip"  # set to None to get all data in the trial


     

    Download data

    Moveshelf provides a public GitHub repository that contains several complete example scripts that are ready to be used.

    Follow the steps in this section to run an example of querying and downloading data from the Moveshelf platform, i.e., download_data.py.

    Get the public repository on your local machine

    Clone Moveshelf’s public GitHub repository to your local machine. You can do this by following these steps.

    Run download_data.py
    • Open download_data.py in Visual Studio Code, follow the instructions, add all relevant information to the script and save the file
    • To run the script, insert python scripts/download_data.py in the terminal and press 'Enter'



     

    Extending the Moveshelf API
    In this section we will show how you can adapt the Moveshelf API to your own use cases by implementing your own functions.
    Prerequisites
    Before implementing this example, ensure that you have performed all necessary setup steps. In particular, you should have:
    Extend the Moveshelf API with a custom query
    If you need queries that are not included in the Moveshelf API, you can create a custom API wrapper that extends it with custom GraphQL queries. This section demonstrates how to create a custom API wrapper defining a new method (getProjectSubjectsCustom) to retrieve, for a given project, all subjects' name, ID, and metadata, and all their sessions' ID, date, and metadata.

    Create a custom API wrapper:
    Create a new Python file (in this example we call it api.py and place it inside a folder called 'api' in the root folder), and define a subclass of MoveshelfApi (see code snippet below). This allows you to extend the existing API by adding custom GraphQL queries. For example, the built-in getProjectSubjects function only retrieves subject names and IDs. In the example below, we define getProjectSubjectsCustom, that extends the built-in getProjectSubjects function to also retrieve subject metadata and session details (ID, date, and metadata).

    from moveshelf_api.api import MoveshelfApi
    
    # Custom Moveshelf API class that extends the existing API
    
    class MoveshelfApiCustomized(MoveshelfApi):
    
        def getProjectSubjectsCustom(self, project_id):
            data = self._dispatch_graphql(
                '''
                query getProjectPatients($projectId: ID!) {
                    node(id: $projectId) {
                        ... on Project {
                            id,
                            name,
                            description,
                            canEdit,
                            patients {
                                id
                                name
                                metadata
                                sessions {
                                    id
                                    date
                                    metadata
                                }
                            }
                        }
                    }
                }
                ''',
                projectId = project_id
            )
            return data['node']['patients']


    Use the custom API in your processing script:
    To use your extended API, import the custom class into your processing script. The example below shows how to call getProjectSubjectsCustom to retrieve additional metadata for all subjects and their sessions.

    parent_folder = os.path.dirname(os.path.dirname(__file__))
    sys.path.append(parent_folder)
    from api.api import MoveshelfApiCustomized
    
    ## Setup the API
    # Load config
    personal_config = os.path.join(parent_folder, "mvshlf-config.json")
    if not os.path.isfile(personal_config):
        raise FileNotFoundError(
            f"Configuration file '{personal_config}' is missing.\n"
            "Ensure the file exists with the correct name and path."
        )
    
    with open(personal_config, "r") as config_file:
        data = json.load(config_file)
    
    # Use custom API
    api = MoveshelfApiCustomized(
            api_key_file=os.path.join(parent_folder, data["apiKeyFileName"]), 
            api_url=data["apiUrl"]
        )
    
    ## General configuration. Set values before running the script
    my_project = "<organizationName/projectName>"  # e.g. support/demoProject
    
    ## Get available projects
    projects = api.getUserProjects()
    
    ## Select the project
    project_names = [project["name"] for project in projects if len(projects) > 0]
    idx_my_project = project_names.index(my_project)
    my_project_id = projects[idx_my_project]["id"]
    
    # Custom query (defined in ../api/api.py) that extracts the metadata of all patients
    # and the metadata of all their sessions within a given project
    subjects = api.getProjectSubjectsCustom(my_project_id)
    
    # Print the subject data
    for subject in subjects:
        print(f"Subject: {subject['name']} (ID: {subject['id']})")
        print(f"Metadata: {subject['metadata']}")
        print("Sessions:")
        for session in subject['sessions']:
            print(f" - Session {session['id']} on {session['date']} with metadata: {session['metadata']}")