Skip to content

Notes API

reptor.api.NotesAPI.NotesAPI

API client for interacting with SysReptor project notes or personal notes.

Example:

python
from reptor import Reptor

reptor = Reptor(
    server=os.environ.get("REPTOR_SERVER"),
    token=os.environ.get("REPTOR_TOKEN"),
    personal_notes=False,
)

# NotesAPI is available as reptor.api.notes, e.g.:
reptor.api.notes.get_notes()

base_endpoint

personal_note

get_notes

python
def get_notes() -> List[Note]

Gets list of all notes in the current context (project notes or personal notes).

Returns:

List of notes for this project or user

Example:

python
reptor.api.notes.get_notes()

get_note

python
def get_note(id: Optional[str] = None, title: Optional[str] = None) -> Optional[Note]

Gets a single note by ID or title.

Parameters:

  • id (str, default: None) – Note ID to retrieve (prioritized over title)
  • title (str, default: None) – Note title to search for

Returns:

Note object if found, None otherwise

Example:

python
# Get note by ID
note = reptor.api.notes.get_note(id="983a7e95-b2d9-4d57-984e-08496264cce8")

# Get note by title
note = reptor.api.notes.get_note(title="My Note")

create_note

python
def create_note(title = 'Note by reptor', text = None, parent: Optional[str] = None, order = None, checked = None, icon = None) -> Note

Creates a new note.

Parameters:

  • title (str, default: 'Note by reptor') – Note title. Defaults to "Note by reptor".
  • text (str, default: None) – Note content. Defaults to None.
  • parent (str, default: None) – Parent note ID for nested notes. Defaults to None.
  • order (int, default: None) – Sort order for the note. Defaults to None.
  • checked (bool, default: None) – Checkbox state for checklist notes. Defaults to None.
  • icon (str, default: None) – Emoji icon for the note. Defaults to None.

Returns:

Created note object

Example:

python
reptor.api.notes.create_note(
    title="My New Note",
    text="This is the content",
    icon="📝"
)

write_note

python
def write_note(id: str = None, title: str = None, text: str, checked: bool = None, icon_emoji: str = None, order: int = 0, timestamp: bool = False, overwrite: bool = False, kwargs = {}) -> Note

Updates notes, appends text to a note (or replaces it).

When id is provided, the existing note with that ID is updated. If title is also provided, the note is renamed as part of the same update. When id is omitted, title is used to look up an existing note or create a new one.

Parameters:

  • id (str, default: None) – Note ID to update
  • title (str, default: None) – Note title for lookup or creation when id is omitted. When id is provided, this becomes the note's new title.
  • text (str, default: '') – Append text to the note. Defaults to empty string.
  • timestamp (bool, default: False) – Prepend timestamp to newly inserted text. Defaults to False.
  • overwrite (bool, default: False) – Replace the note's existing text with text instead of appending. Use for content that represents a current state rather than a running log, e.g. a checklist whose rows change. Defaults to False.
  • checked (bool, default: None) – Checkbox state for checklist notes.
  • icon_emoji (str, default: None) – Emoji icon for the note.
  • order (int, default: 0) – Sort order for the note. Defaults to 0.

Returns:

The uploaded note after a successful PUT.

Example:

python
reptor.api.notes.write_note(
    title="Security Finding",
    text="Found vulnerability in authentication",
    timestamp=True
)

# Replace the note content instead of appending
reptor.api.notes.write_note(
    title="Checklist",
    text="- [x] Recon\n- [ ] Exploit",
    overwrite=True,
)

# Update a note by ID and rename it in the same call
reptor.api.notes.write_note(
    id="983a7e95-b2d9-4d57-984e-08496264cce8",
    title="Updated Title",
    text="Renamed while appending new content",
)

write_note_templates

python
def write_note_templates(note_templates: Union[NoteTemplate, List[NoteTemplate]], timestamp: bool = True, overwrite: bool = False, kwargs = {}) -> Optional[Note]

Writes note templates, appending to existing notes by default.

Parameters:

  • note_templates (Union[NoteTemplate, List[NoteTemplate]]) – A single note template or a list of them.
  • timestamp (bool, default: True) – Prepend timestamp to newly inserted text. Defaults to True.
  • overwrite (bool, default: False) – Replace the existing note text instead of appending to it. Applies to child notes as well. Defaults to False.

Returns:

The last root note uploaded in this call (children are written recursively and are not returned).

set_icon

python
def set_icon(note_id: str = None, icon: str = None, id: str = None)

Sets an emoji icon for a note.

Parameters:

  • note_id (str, default: None) – Note ID
  • icon (str, default: None) – Emoji character to set as icon
  • id ((str, deprecated), default: None) – Deprecated. Use note_id instead.

Example:

python
reptor.api.notes.set_icon(note_id="983a7e95-b2d9-4d57-984e-08496264cce8", icon="🔒")

upload_file

python
def upload_file(file: Optional[IO] = None, content: Optional[bytes] = None, filename: Optional[str] = None, caption: Optional[str] = None, note_id: Optional[str] = None, note_title: Optional[str] = None, parent_title: Optional[str] = None, kwargs = {})

Uploads a file and append to note.

Parameters:

  • file (IO, default: None) – File object to upload
  • content (bytes, default: None) – File content as bytes
  • filename (str, default: None) – Name for the uploaded file
  • caption (str, default: None) – Caption for the file link
  • note_id (str, default: None) – ID of note to upload to
  • note_title (str, default: None) – Title of note to upload to
  • parent_title (str, default: None) – Parent note title for organization
  • **kwargs (default: {}) – Additional parameters for note writing

Example:

python
# Upload from file
with open("screenshot.png", "rb") as f:
    reptor.api.notes.upload_file(
        file=f,
        note_title="Evidence",
        caption="Login page screenshot"
    )

# Upload from bytes
reptor.api.notes.upload_file(
    content=b"file content",
    filename="data.txt",
    note_title="Files"
)

render

python
def render(note_id: str = None, id: str = None) -> bytes

Renders a note to PDF format.

Parameters:

  • note_id (str, default: None) – Note ID to render
  • id ((str, deprecated), default: None) – Deprecated. Use note_id instead.

Returns:

PDF content as bytes

Example:

python
pdf_data = reptor.api.notes.render(note_id="note-uuid-here")
with open("note.pdf", "wb") as f:
    f.write(pdf_data)

delete_note

python
def delete_note(id: str)

Deletes a note by ID.

Parameters:

  • id (str) – Note ID to delete

Example:

python
reptor.api.notes.delete_note("983a7e95-b2d9-4d57-984e-08496264cce8")

duplicate

python
def duplicate(note_id: str = None, id: str = None) -> Note

Creates a note duplicate.

Parameters:

  • note_id (str, default: None) – Note ID to duplicate
  • id ((str, deprecated), default: None) – Deprecated. Use note_id instead.

Returns:

Duplicated note object

Example:

python
duplicate_note = reptor.api.notes.duplicate(note_id="note-uuid-here")
print(f"Duplicated note: {duplicate_note.title}")

get_note_by_title

python
def get_note_by_title(title, parent = None, parent_title = None, any_parent = False) -> Optional[Note]

get_or_create_note_by_title

python
def get_or_create_note_by_title(title, parent = None, parent_title = None, icon = None) -> Note

Source

python
class NotesAPI(APIClient):
    """API client for interacting with SysReptor project notes or personal notes.

    Example:
        ```python
        from reptor import Reptor

        reptor = Reptor(
            server=os.environ.get("REPTOR_SERVER"),
            token=os.environ.get("REPTOR_TOKEN"),
            personal_notes=False,
        )

        # NotesAPI is available as reptor.api.notes, e.g.:
        reptor.api.notes.get_notes()
        ```
    """

    def __init__(self, **kwargs) -> None:
        super().__init__(require_project_id=False, **kwargs)

        if self.personal_note:
            self.base_endpoint = urljoin(
                self.reptor.get_config().get_server(),
                "api/v1/pentestusers/self/notes/",
            )
        elif self.project_id:
            self.base_endpoint = urljoin(
                self.reptor.get_config().get_server(),
                f"api/v1/pentestprojects/{self.project_id}/notes/",
            )

    @property
    def personal_note(self):
        return self.reptor.get_config().get("personal_note")

    def get_notes(self) -> typing.List[Note]:
        """Gets list of all notes in the current context (project notes or personal notes).

        Returns:
            List of notes for this project or user

        Example:
            ```python
            reptor.api.notes.get_notes()
            ```
        """
        response = self.get(self.base_endpoint)
        notes = list()
        for note_data in response.json():
            notes.append(Note(note_data))
        return notes

    def get_note(
        self,
        id: typing.Optional[str] = None,
        title: typing.Optional[str] = None,
    ) -> typing.Optional[Note]:
        """Gets a single note by ID or title.

        Args:
            id (str, optional): Note ID to retrieve (prioritized over title)
            title (str, optional): Note title to search for

        Returns:
            Note object if found, None otherwise

        Example:
            ```python
            # Get note by ID
            note = reptor.api.notes.get_note(id="983a7e95-b2d9-4d57-984e-08496264cce8")

            # Get note by title
            note = reptor.api.notes.get_note(title="My Note")
            ```
        """
        for note in self.get_notes():
            if id:
                if note.id == id:
                    return note
            elif title:
                if note.title == title:
                    return note
            else:
                raise ValueError("Either id or title must be provided")

    def create_note(
        self,
        title="Note by reptor",
        text=None,
        parent: typing.Optional[str] = None,
        order=None,
        checked=None,
        icon=None,
    ) -> Note:
        """Creates a new note.

        Args:
            title (str, optional): Note title. Defaults to "Note by reptor".
            text (str, optional): Note content. Defaults to None.
            parent (str, optional): Parent note ID for nested notes. Defaults to None.
            order (int, optional): Sort order for the note. Defaults to None.
            checked (bool, optional): Checkbox state for checklist notes. Defaults to None.
            icon (str, optional): Emoji icon for the note. Defaults to None.

        Returns:
            Created note object

        Example:
            ```python
            reptor.api.notes.create_note(
                title="My New Note",
                text="This is the content",
                icon="📝"
            )
            ```
        """
        if title is None:
            raise ValueError("Note title must not be null.")
        note = self.post(
            self.base_endpoint,
            json={
                "order": order,
                "parent": parent or None,
                "checked": checked,
                "title": title,
                "text": text or "",
            },
        ).json()
        if icon:
            self.set_icon(note_id=note.get("id"), icon=icon)
        elif title == "Uploads":
            self.set_icon(note_id=note.get("id"), icon="📤")
        return Note(note)

    def write_note(
        self,
        id: str = None,
        title: str = None,
        text: str = "",
        checked: bool = None,
        icon_emoji: str = None,
        order: int = 0,
        timestamp: bool = False,
        overwrite: bool = False,
        **kwargs,
    ) -> Note:
        """Updates notes, appends text to a note (or replaces it).

        When ``id`` is provided, the existing note with that ID is updated. If
        ``title`` is also provided, the note is renamed as part of the same
        update. When ``id`` is omitted, ``title`` is used to look up an
        existing note or create a new one.

        Args:
            id (str, optional): Note ID to update
            title (str, optional): Note title for lookup or creation when ``id`` is
                omitted. When ``id`` is provided, this becomes the note's new title.
            text (str, optional): Append text to the note. Defaults to empty string.
            timestamp (bool, optional): Prepend timestamp to newly inserted text. Defaults to False.
            overwrite (bool, optional): Replace the note's existing text with `text`
                instead of appending. Use for content that represents a current state
                rather than a running log, e.g. a checklist whose rows change. Defaults to False.
            checked (bool, optional): Checkbox state for checklist notes.
            icon_emoji (str, optional): Emoji icon for the note.
            order (int, optional): Sort order for the note. Defaults to 0.

        Returns:
            The uploaded note after a successful PUT.

        Example:
            ```python
            reptor.api.notes.write_note(
                title="Security Finding",
                text="Found vulnerability in authentication",
                timestamp=True
            )

            # Replace the note content instead of appending
            reptor.api.notes.write_note(
                title="Checklist",
                text="- [x] Recon\\n- [ ] Exploit",
                overwrite=True,
            )

            # Update a note by ID and rename it in the same call
            reptor.api.notes.write_note(
                id="983a7e95-b2d9-4d57-984e-08496264cce8",
                title="Updated Title",
                text="Renamed while appending new content",
            )
            ```
        """
        note_template = NoteTemplate.from_kwargs(
            id=id,
            title=title,
            text=text,
            checked=checked, 
            icon_emoji=icon_emoji, 
            order=order,
            **kwargs
        )
        return self.write_note_templates(
            note_template, timestamp=timestamp, overwrite=overwrite
        )

    def write_note_templates(
        self,
        note_templates: typing.Union[NoteTemplate, typing.List[NoteTemplate]],
        timestamp: bool = True,
        overwrite: bool = False,
        **kwargs,
    ) -> typing.Optional[Note]:
        """Writes note templates, appending to existing notes by default.

        Args:
            note_templates: A single note template or a list of them.
            timestamp (bool, optional): Prepend timestamp to newly inserted text. Defaults to True.
            overwrite (bool, optional): Replace the existing note text instead of
                appending to it. Applies to child notes as well. Defaults to False.

        Returns:
            The last root note uploaded in this call (children are written
            recursively and are not returned).
        """
        if not isinstance(note_templates, list):
            note_templates = [note_templates]
        written = None
        for note_template in note_templates:
            if note_template.id:
                new_note = False
                note = self.get_note(id=note_template.id)
                if not note:
                    raise ValueError(f'Note with ID "{note_template.id}" does not exist.')
            else:
                if note_template.parent_title and not note_template.parent:
                    note_template.parent = self.get_or_create_note_by_title(
                        note_template.parent_title
                    ).id

                note = None
                if not note_template.force_new:
                    note = self.get_note_by_title(
                        note_template.title,
                        parent=note_template.parent,
                    )
                if note is None:
                    new_note = True
                    note = self.create_note(
                        title=note_template.title,
                        parent=note_template.parent or None,
                    )
                else:
                    new_note = False
            self.debug(f"Got note from server {note.title} ({note.id})")

            # Prepare note (append content to existing note, or replace it)
            note_text = "" if overwrite else (note.text + "\n\n" if note.text else "")
            if (
                timestamp and note_template.text
            ):  # Only add timestamp if there is content
                note_text += f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]"
                if "\n" in note_template.text:
                    if not note_template.text.startswith("\n"):
                        note_text += "\n"
                else:
                    note_text += ": "
            note_text += note_template.text or ""

            if new_note:
                upload_note = Note.from_note_template(note_template)
                upload_note.parent = note.parent
                upload_note.id = note.id
                upload_note.text = note_text
            else:
                upload_note = note
                if note_template.title:
                    upload_note.title = note_template.title
                if note_template.checked is not None:
                    upload_note.checked = note_template.checked
                if note_template.icon_emoji:
                    upload_note.icon_emoji = note_template.icon_emoji
                if note_template.order:
                    upload_note.order = note_template.order
                upload_note.text = note_text

            # Upload note and children recursively
            self._upload_note(upload_note, **kwargs)
            written = upload_note
            for child in note_template.children:
                child.parent = note.id
                self.write_note_templates(
                    child, timestamp=timestamp, overwrite=overwrite, **kwargs
                )
        return written

    def set_icon(self, note_id: str = None, icon: str = None, id: str = None):
        """Sets an emoji icon for a note.

        Args:
            note_id (str): Note ID
            icon (str): Emoji character to set as icon
            id (str, deprecated): Deprecated. Use note_id instead.

        Example:
            ```python
            reptor.api.notes.set_icon(note_id="983a7e95-b2d9-4d57-984e-08496264cce8", icon="🔒")
            ```
        """
        # Handle deprecated 'id' parameter
        if id is not None:
            self.log.warning(
                "The 'id' parameter is deprecated and will be removed in a future version. "
                "Use 'note_id' instead."
            )
            if note_id is None:
                note_id = id

        if note_id is None:
            raise ValueError("note_id parameter is required")

        url = urljoin(self.base_endpoint, f"{note_id}/")
        self.put(url, json={"icon_emoji": icon})

    def upload_file(
        self,
        file: typing.Optional[typing.IO] = None,
        content: typing.Optional[bytes] = None,
        filename: typing.Optional[str] = None,
        caption: typing.Optional[str] = None,
        note_id: typing.Optional[str] = None,
        note_title: typing.Optional[str] = None,
        parent_title: typing.Optional[str] = None,
        **kwargs,
    ):
        """Uploads a file and append to note.

        Args:
            file (typing.IO, optional): File object to upload
            content (bytes, optional): File content as bytes
            filename (str, optional): Name for the uploaded file
            caption (str, optional): Caption for the file link
            note_id (str, optional): ID of note to upload to
            note_title (str, optional): Title of note to upload to
            parent_title (str, optional): Parent note title for organization
            **kwargs: Additional parameters for note writing

        Example:
            ```python
            # Upload from file
            with open("screenshot.png", "rb") as f:
                reptor.api.notes.upload_file(
                    file=f,
                    note_title="Evidence",
                    caption="Login page screenshot"
                )

            # Upload from bytes
            reptor.api.notes.upload_file(
                content=b"file content",
                filename="data.txt",
                note_title="Files"
            )
            ```
        """
        # Upload the file
        upload_result = self._upload_file(
            file=file,
            content=content,
            filename=filename,
        )

        if upload_result is None:
            return

        filename = upload_result["filename"]
        filepath = upload_result["filepath"]
        response_json = upload_result["response"]

        # Prepare note content
        if not note_id:
            note_id = self.get_or_create_note_by_title(
                note_title or "Uploads", parent_title=parent_title
            ).id

        is_image = True if response_json.get("resource_type") == "image" else False
        if is_image:
            markdown_link = f"\n![{caption or filename}]({filepath})"
        else:
            markdown_link = f"\n[{caption or filename}]({filepath})"

        # Write to note
        self.write_note(
            id=note_id,
            text=markdown_link,
            **kwargs,
        )

    def _upload_file(
        self,
        file: typing.Optional[typing.IO] = None,
        content: typing.Optional[bytes] = None,
        filename: typing.Optional[str] = None,
    ) -> typing.Optional[dict[str, typing.Any]]:
        """File upload without adding a reference (e.g., in a note).
        Note that unreferences files will be cleaned up by SysReptor in the course of regular cleanup tasks.

        Args:
            file (typing.IO, optional): File object to upload
            content (bytes, optional): File content as bytes
            filename (str, optional): Name for the uploaded file

        Returns:
            dict: Upload result with 'filename', 'filepath', and 'response' keys
        """
        assert file or content
        assert not (file and content)

        if file:
            if file.name == "<stdin>":
                self.display("Reading from stdin...")
            elif not filename:
                filename = basename(file.name)
            # TODO this might be streamed to not load entire file to memory
            try:
                content = file.buffer.read()  # type: ignore
            except AttributeError:
                content = file.read()
            if not filename:
                filetype = guess_filetype(content) or "dat"
                filename = f"data.{filetype}"

            if not content:
                self.warning(f"{file.name} is empty. Will not upload.")
                return None

        if self.personal_note:
            url = urljoin(self.base_endpoint, "upload/")
        else:
            url = urljoin(self.base_endpoint.rsplit("/", 2)[0], "upload/")

        response_json = self.post(
            url, files={"file": (filename, content)}, json_content=False
        ).json()

        # Parse file path based on resource type
        is_image = True if response_json.get("resource_type") == "image" else False
        if is_image:
            filepath = f"/images/name/{response_json['name']}"
        else:
            filepath = f"/files/name/{response_json['name']}"

        return {"filename": filename, "filepath": filepath, "response": response_json}

    def render(
        self,
        note_id: str = None,
        id: str = None,
    ) -> bytes:
        """Renders a note to PDF format.

        Args:
            note_id (str): Note ID to render
            id (str, deprecated): Deprecated. Use note_id instead.

        Returns:
            PDF content as bytes

        Example:
            ```python
            pdf_data = reptor.api.notes.render(note_id="note-uuid-here")
            with open("note.pdf", "wb") as f:
                f.write(pdf_data)
            ```
        """
        # Handle deprecated 'id' parameter
        if id is not None:
            self.log.warning(
                "The 'id' parameter is deprecated and will be removed in a future version. "
                "Use 'note_id' instead."
            )
            if note_id is None:
                note_id = id

        if note_id is None:
            raise ValueError("note_id parameter is required")

        url = urljoin(self.base_endpoint, f"{note_id}/export-pdf/")
        response = self.post(url, timeout=self.reptor.get_config().get_api_timeout_long())
        response.raise_for_status()
        return response.content

    def delete_note(self, id: str):
        """Deletes a note by ID.

        Args:
            id (str): Note ID to delete

        Example:
            ```python
            reptor.api.notes.delete_note("983a7e95-b2d9-4d57-984e-08496264cce8")
            ```
        """
        url = urljoin(self.base_endpoint, f"{id}/")
        self.delete(url)

    def duplicate(
        self,
        note_id: str = None,
        id: str = None,
    ) -> Note:
        """Creates a note duplicate.

        Args:
            note_id (str): Note ID to duplicate
            id (str, deprecated): Deprecated. Use note_id instead.

        Returns:
            Duplicated note object

        Example:
            ```python
            duplicate_note = reptor.api.notes.duplicate(note_id="note-uuid-here")
            print(f"Duplicated note: {duplicate_note.title}")
            ```
        """
        # Handle deprecated 'id' parameter
        if id is not None:
            self.log.warning(
                "The 'id' parameter is deprecated and will be removed in a future version. "
                "Use 'note_id' instead."
            )
            if note_id is None:
                note_id = id

        if note_id is None:
            raise ValueError("note_id parameter is required")

        url = urljoin(self.base_endpoint, f"{note_id}/copy/")
        response = self.post(url)
        response.raise_for_status()
        return Note(response.json())

    def get_note_by_title(
        self,
        title,
        parent=None,  # Preferred over parent_title
        parent_title=None,
        any_parent=False,
    ) -> typing.Optional[Note]:
        if not parent and parent_title:
            try:
                parent = self.get_note_by_title(parent_title, any_parent=True).id  # type: ignore
            except AttributeError:
                raise ValueError(f'Parent note "{parent_title}" does not exist.')
        notes_list = self.get_notes()

        for note in notes_list:
            if note.title == title and (note.parent == parent or any_parent):
                break
        else:
            return None
        return note

    def get_or_create_note_by_title(
        self,
        title,
        parent=None,  # Preferred over parent_title
        parent_title=None,
        icon=None,
    ) -> Note:
        if not parent and parent_title:
            parent = self.get_or_create_note_by_title(
                parent_title, icon=icon
            ).id
        note = self.get_note_by_title(title, parent=parent)
        if not note:
            # Note does not exist. Create.
            note = self.create_note(title=title, parent=parent, icon=icon)
        return note

    def _upload_note(
        self,
        note: Note,
    ):
        url = urljoin(self.base_endpoint, note.id, "")
        r = self.put(url, json=note.to_dict())

        try:
            r.raise_for_status()
            self.display(f'Note written to "{note.title}".')
        except HTTPError as e:
            raise HTTPError(
                f'{str(e)} Are you uploading binary content to note? (Try "file" subcommand)'
            ) from e