diff --git a/examples/use-cases/audit-trail/Get History of an Entity.ipynb b/examples/use-cases/audit-trail/Get History of an Entity.ipynb
new file mode 100644
index 00000000..6b6358d3
--- /dev/null
+++ b/examples/use-cases/audit-trail/Get History of an Entity.ipynb
@@ -0,0 +1,586 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "c74f906c",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ "\n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "from lusidtools.jupyter_tools import toggle_code\n",
+ "\n",
+ "\"\"\"Get History of an Entity\n",
+ "\n",
+ "This notebook shows how to get the full history of an entity using the asAt history of an entity.\n",
+ "\n",
+ "Attributes\n",
+ "----------\n",
+ "entities\n",
+ "history\n",
+ "instruments\n",
+ "\"\"\"\n",
+ "\n",
+ "toggle_code(\"Hide docstring\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "586b08a8",
+ "metadata": {},
+ "source": [
+ "# Get History of an Entity\n",
+ "\n",
+ "In this notebook, we cover how to get the full history of an entity.\n",
+ "\n",
+ "In this case, our entity is an `Instrument`, but you can apply this technique to the following entities:\n",
+ "- `Abor`\n",
+ "- `Abor Config`\n",
+ "- `Allocation`\n",
+ "- `Block`\n",
+ "- `Corporate Action Source`\n",
+ "- `Custom Entity`\n",
+ "- `Execution`\n",
+ "- `Instrument`\n",
+ "- `Legal Entity`\n",
+ "- `Order`\n",
+ "- `Order Graph`\n",
+ "- `Order Instruction`\n",
+ "- `Package`\n",
+ "- `Participation`\n",
+ "- `Person`\n",
+ "- `Placement`\n",
+ "- `Portfolio`\n",
+ "- `Portfolio Group`\n",
+ "- `Relationship Definition`\n",
+ "- `Transaction Fee`\n",
+ "- `Transaction Portfolio`\n",
+ "\n",
+ "\n",
+ "To get the history of an entity, the steps are as follows:\n",
+ "1. [Create Entity](#1.-Create-Entity)\n",
+ "2. [Modify Entity](#2.-Modify-Entity)\n",
+ "3. [Get History of Entity](#3.-Get-History-of-Entity)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0d12bab6",
+ "metadata": {},
+ "source": [
+ "## Setup\n",
+ "\n",
+ "Firstly, let's import LUSID packages"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "b57bbce5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Import generic python modules\n",
+ "import pandas as pd\n",
+ "from datetime import datetime, timedelta\n",
+ "import pytz\n",
+ "import os\n",
+ "\n",
+ "# Import key modules from the LUSID package\n",
+ "import lusid as lu\n",
+ "import lusid.models as lm\n",
+ "from lusidtools.cocoon.cocoon import load_from_data_frame\n",
+ "from lusidtools.cocoon.cocoon_printer import format_quotes_response\n",
+ "from lusidjam import RefreshingToken\n",
+ "\n",
+ "# Set the secrets path\n",
+ "secrets_path = os.getenv(\"FBN_SECRETS_PATH\")\n",
+ "\n",
+ "# For running the notebook locally\n",
+ "if secrets_path is None:\n",
+ " secrets_path = os.path.join(os.path.dirname(os.getcwd()), \"secrets.json\")\n",
+ "\n",
+ "# Authenticate our user and create our API client\n",
+ "api_factory = lu.utilities.ApiClientFactory(\n",
+ " token=RefreshingToken(),\n",
+ " api_secrets_filename=secrets_path\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5dcc2b9e",
+ "metadata": {},
+ "source": [
+ "Next, let's create our API."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "abece5b4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# LUSID Variable Definitions\n",
+ "instruments_api = api_factory.build(lu.api.InstrumentsApi)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "15814f45",
+ "metadata": {},
+ "source": [
+ "Finally, we declare some helper functions."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "cb736e90",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def create_instrument(name, client_internal, figi):\n",
+ " instr_result = instruments_api.upsert_instruments(\n",
+ " request_body={\n",
+ " name: lm.InstrumentDefinition(\n",
+ " name=name,\n",
+ " identifiers={\n",
+ " \"ClientInternal\": lm.InstrumentIdValue(value=client_internal),\n",
+ " \"Figi\": lm.InstrumentIdValue(value=figi),\n",
+ " }\n",
+ " )\n",
+ " }\n",
+ " )\n",
+ " # luid\n",
+ " return instr_result.values[name].lusid_instrument_id\n",
+ "\n",
+ "def update_instrument(luid, figi):\n",
+ " update_result = instruments_api.update_instrument_identifier(\n",
+ " identifier_type=\"LusidInstrumentId\",\n",
+ " identifier=luid,\n",
+ " update_instrument_identifier_request=lm.UpdateInstrumentIdentifierRequest(\n",
+ " type=\"Figi\",\n",
+ " value=figi\n",
+ " )\n",
+ " )\n",
+ " # luid\n",
+ " return update_result.lusid_instrument_id\n",
+ "\n",
+ "def get_asat(luid, as_at=None):\n",
+ " response = instruments_api.get_instrument(\n",
+ " identifier_type=\"LusidInstrumentId\",\n",
+ " identifier=luid,\n",
+ " as_at=as_at\n",
+ " )\n",
+ " return response"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1a4daa3b",
+ "metadata": {},
+ "source": [
+ "## 1. Create Entity\n",
+ "\n",
+ "First create the entity. In this case we are creating an instrument."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "37ac4898",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'LUID_00003IZ7'"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "luid = create_instrument(name=\"AsAtInstrument\", client_internal=f\"AS_AT_INSTRUMENT\", figi=\"Original\")\n",
+ "luid"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "64c78168",
+ "metadata": {},
+ "source": [
+ "## 2. Modify Entity\n",
+ "\n",
+ "Let's modify the entity. For our example, we will be changing the FIGI of the instrument."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "904d95e9",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'LUID_00003IZ7'"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "update_instrument(luid=luid, figi=\"Edit1\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "899c0a46",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'LUID_00003IZ7'"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "update_instrument(luid=luid, figi=\"Edit2\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "e09c24bd",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'LUID_00003IZ7'"
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "update_instrument(luid=luid, figi=\"Original\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "deb949c6",
+ "metadata": {},
+ "source": [
+ "## 3. Get History of Entity\n",
+ "\n",
+ "In order to get the history of an entity, we need to use the version `asAt` data that is returned.\n",
+ "\n",
+ "The data looks like the following:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "588475d9",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'as_at_created': datetime.datetime(2023, 5, 3, 13, 39, 16, 512886, tzinfo=tzlocal()),\n",
+ " 'as_at_date': datetime.datetime(2023, 5, 3, 13, 51, 14, 991585, tzinfo=tzlocal()),\n",
+ " 'as_at_modified': datetime.datetime(2023, 5, 3, 13, 40, 8, 387528, tzinfo=tzlocal()),\n",
+ " 'as_at_version_number': 4,\n",
+ " 'effective_from': datetime.datetime(1, 1, 1, 0, 0, tzinfo=tzlocal()),\n",
+ " 'user_id_created': '00uieujafoYdmkDSx2p7',\n",
+ " 'user_id_modified': '00uieujafoYdmkDSx2p7'}"
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "resp = get_asat(luid).version\n",
+ "resp"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "686c5c0d",
+ "metadata": {},
+ "source": [
+ "The key aspect is the `as_at_modified` value. If we fetch our entity from a date and time that is slightly before this value, we will see we have a lower `as_at_version_number`. See the following example and how the version number changes from `4` to `3`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "cb7b0940",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'as_at_created': datetime.datetime(2023, 5, 3, 13, 39, 16, 512886, tzinfo=tzlocal()),\n",
+ " 'as_at_date': datetime.datetime(2023, 5, 3, 13, 40, 8, 386528, tzinfo=tzlocal()),\n",
+ " 'as_at_modified': datetime.datetime(2023, 5, 3, 13, 40, 7, 246434, tzinfo=tzlocal()),\n",
+ " 'as_at_version_number': 3,\n",
+ " 'effective_from': datetime.datetime(1, 1, 1, 0, 0, tzinfo=tzlocal()),\n",
+ " 'user_id_created': '00uieujafoYdmkDSx2p7',\n",
+ " 'user_id_modified': '00uieujafoYdmkDSx2p7'}"
+ ]
+ },
+ "execution_count": 16,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "current_as_at = resp.as_at_modified\n",
+ "previous_as_at = current_as_at - timedelta(milliseconds=1)\n",
+ "\n",
+ "get_asat(luid, previous_as_at).version"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b7aa8421",
+ "metadata": {},
+ "source": [
+ "We can do this recursively to get the **full history** of an entity."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "7ca9e8c8",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " date | \n",
+ " version_number | \n",
+ " modified_id | \n",
+ " client_internal | \n",
+ " figi | \n",
+ " luid | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 3 | \n",
+ " 2023-05-03 13:39:16.512886+00:00 | \n",
+ " 1 | \n",
+ " 00uieujafoYdmkDSx2p7 | \n",
+ " AS_AT_INSTRUMENT | \n",
+ " Original | \n",
+ " LUID_00003IZ7 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " 2023-05-03 13:39:16.701563+00:00 | \n",
+ " 2 | \n",
+ " 00uieujafoYdmkDSx2p7 | \n",
+ " AS_AT_INSTRUMENT | \n",
+ " Edit1 | \n",
+ " LUID_00003IZ7 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " 2023-05-03 13:40:07.246434+00:00 | \n",
+ " 3 | \n",
+ " 00uieujafoYdmkDSx2p7 | \n",
+ " AS_AT_INSTRUMENT | \n",
+ " Edit2 | \n",
+ " LUID_00003IZ7 | \n",
+ "
\n",
+ " \n",
+ " | 0 | \n",
+ " 2023-05-03 13:40:08.387528+00:00 | \n",
+ " 4 | \n",
+ " 00uieujafoYdmkDSx2p7 | \n",
+ " AS_AT_INSTRUMENT | \n",
+ " Original | \n",
+ " LUID_00003IZ7 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " date version_number modified_id \\\n",
+ "3 2023-05-03 13:39:16.512886+00:00 1 00uieujafoYdmkDSx2p7 \n",
+ "2 2023-05-03 13:39:16.701563+00:00 2 00uieujafoYdmkDSx2p7 \n",
+ "1 2023-05-03 13:40:07.246434+00:00 3 00uieujafoYdmkDSx2p7 \n",
+ "0 2023-05-03 13:40:08.387528+00:00 4 00uieujafoYdmkDSx2p7 \n",
+ "\n",
+ " client_internal figi luid \n",
+ "3 AS_AT_INSTRUMENT Original LUID_00003IZ7 \n",
+ "2 AS_AT_INSTRUMENT Edit1 LUID_00003IZ7 \n",
+ "1 AS_AT_INSTRUMENT Edit2 LUID_00003IZ7 \n",
+ "0 AS_AT_INSTRUMENT Original LUID_00003IZ7 "
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "as_at = pytz.utc.localize(datetime.now())\n",
+ "instr_data = {\n",
+ " \"date\": [],\n",
+ " \"version_number\": [],\n",
+ " \"modified_id\": [],\n",
+ " \"client_internal\": [],\n",
+ " \"figi\": [],\n",
+ " \"luid\": []\n",
+ "}\n",
+ "\n",
+ "while True:\n",
+ " resp = get_asat(luid, as_at)\n",
+ " ids = resp.identifiers\n",
+ " version = resp.version\n",
+ " # add instr data to df\n",
+ " instr_data['date'].append(version.as_at_modified)\n",
+ " instr_data['version_number'].append(version.as_at_version_number)\n",
+ " instr_data['modified_id'].append(version.user_id_modified)\n",
+ " instr_data['client_internal'].append(ids['ClientInternal'])\n",
+ " instr_data['figi'].append(ids['Figi'] if 'Figi' in ids else None)\n",
+ " instr_data['luid'].append(ids['LusidInstrumentId'])\n",
+ " # final version, end\n",
+ " if version.as_at_version_number == 1:\n",
+ " break\n",
+ " # get previous version by going slightly before modified date\n",
+ " as_at = version.as_at_modified - timedelta(milliseconds=1)\n",
+ "\n",
+ "pd.DataFrame(data=instr_data).sort_values(by='date')"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.7"
+ },
+ "toc": {
+ "base_numbering": 1,
+ "nav_menu": {},
+ "number_sections": false,
+ "sideBar": true,
+ "skip_h1_title": false,
+ "title_cell": "Table of Contents",
+ "title_sidebar": "Contents",
+ "toc_cell": false,
+ "toc_position": {},
+ "toc_section_display": true,
+ "toc_window_display": false
+ },
+ "varInspector": {
+ "cols": {
+ "lenName": 16,
+ "lenType": 16,
+ "lenVar": 40
+ },
+ "kernels_config": {
+ "python": {
+ "delete_cmd_postfix": "",
+ "delete_cmd_prefix": "del ",
+ "library": "var_list.py",
+ "varRefreshCmd": "print(var_dic_list())"
+ },
+ "r": {
+ "delete_cmd_postfix": ") ",
+ "delete_cmd_prefix": "rm(",
+ "library": "var_list.r",
+ "varRefreshCmd": "cat(var_dic_list()) "
+ }
+ },
+ "types_to_exclude": [
+ "module",
+ "function",
+ "builtin_function_or_method",
+ "instance",
+ "_Feature"
+ ],
+ "window_display": false
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}