Skip to main content

easyvista-python-client

CI Coverage License Python Docs

Typed Python client for the EasyVista Service Manager REST API. Sync + async, Pydantic models, Bearer or Basic auth.

While the package is preparing for 1.0, breaking changes may land between minor versions; a deprecation policy will follow the 1.0 release. A patch release never carries one. Each breaking change is marked **BREAKING** in its CHANGELOG.md section with the reasoning, so read the section for the version you are moving to.

Documentation

Full documentation: https://easyvista-python-client.readthedocs.io/

Build it locally with pip install -e ".[docs]" then sphinx-build -b html -W docs docs/_build/html.

Install

pip install easyvista-python-client

Python 3.11 or newer. 0.4.0 dropped 3.10; on 3.10, pip installs 0.3.0, which has no content extra: the converter below needs 3.11 or newer.

To read and write memo text as Markdown, add the optional content extra, which brings a Markdown <-> HTML converter, easyvista_python_client.content. Its Markdown is CommonMark with GFM tables:

pip install "easyvista-python-client[content]"

Usage (sync)

from easyvista_python_client import (
    EasyvistaClient,
    EasyvistaConfig,
    PostRequest,
    WorkflowEffect,
    ev_equals_filter,
)

# `account` is the instance id in the API root (https://host/api/v1/12345), not a username.
config = EasyvistaConfig(server="https://my.easyvista.com", account="12345", token="...")
with EasyvistaClient(config) as client:
    # catalog_code, the *_id values and the close status_guid are
    # instance-specific -- `client.describe_instance()` finds them for you.
    # `external_reference` is your own marker, and it is what lets you
    # reconcile a create that failed: see the note below this block.
    ticket = client.create_ticket(
        PostRequest(
            catalog_code="INC_STANDARD",
            title="Printer down",
            description="The 3rd-floor printer is offline",
            # The vendor documents `origin` as a channel NAME, not an id --
            # the one create field with a portable form. An int is also
            # accepted (measured on one instance) and passes through as sent.
            origin="Phone",
            department_id=9,
            urgency_id=8,
            impact_id=28,
            external_reference="MYAPP-0001",  # your own marker; set it always
        )
    )
    fetched = client.get_ticket(ticket.rfc_number)
    open_status = ev_equals_filter("STATUS_ID", 3)
    results = client.search_tickets(search=open_status, max_rows=50)

    # page through everything with the iterator (follows the API's offset paging)
    for t in client.iter_tickets(search=open_status, page_size=100, max_records=1000):
        ...  # async: `async for t in client.iter_tickets(...)`

    # close only when closing is the intent: it interrupts the ticket's workflow.
    client.close_ticket(
        ticket.rfc_number,
        allow_workflow_effect=WorkflowEffect.INTERRUPTS,
        status_guid="{00000000-0000-0000-0000-000000000000}",
        delete_actions=1,
        comment="Resolved",
    )

A create needs a subject: catalog_guid (the vendor's preferred identifier) or catalog_code. Anything beyond that is catalog-specific and enforced server-side, so a field a given catalog insists on raises EasyvistaValidationError (HTTP 590, code 2013) — it is not retried, and the message names no field.

Do not retry that 590 blindly. Measured on one instance (2026-08-25), a rejected create may still have created the ticket: 12 attempts returned 3 RFC_NUMBERs and afterwards all 12 tickets existed. A 590 means possibly created, never not created. Set external_reference on every create and reconcile by that marker — it survives the failed insert and is searchable.

Comments and actions

An action is a unit of work, and it is born open — an open action shows in the UI as a pending row with its text not displayed, which reads as though the note was lost. A comment is an action that has been ended.

from easyvista_python_client import PostAction, PostTask

# A COMMENT: `create_task` posts the same record already ended, in one call.
# Put the text in `description` -- the UI renders one field per action and
# `description` shadows `comment`, so text in `comment` beside a populated
# `description` is stored, readable through the API, and displayed to nobody.
client.create_task(
    rfc,
    PostTask(action_type_id=94, group_id=3, description="Investigating now."),
)

# WORK SOMEONE MUST STILL DO: create it open, then end it when it is done.
client.create_action(
    rfc,
    PostAction(action_type_id=94, group_id=3, description="Chase the supplier."),
)
client.end_action(
    rfc,
    action_id=1234,                    # not recoverable from the create response
    start_date="01/09/2026 17:00:00",  # your instance's format, not ISO 8601
    end_date="01/09/2026 17:15:00",
    elapsed_time=15,                   # MINUTES
)

There is no private-comment flag. Visibility is carried by the action type, which is per-deployment — read the ids off existing actions with client.discover("ACTION_TYPE") rather than hardcoding one.

end_action on a workflow action changes the ticket. Ending your own action only ends it; ending the ticket's open workflow step advances the workflow and moves the ticket's status. Naming action_id is therefore required — the vendor's id-less "end every open action" form is behind an explicit end_all=True. Ending a workflow step, or ending every open action with end_all=True, is refused unless the call passes allow_workflow_effect=WorkflowEffect.ADVANCES. Ending an action you created yourself needs no opt-in, with one unmeasured exception: whether an action create_action creates under the workflow step carries a WORKFLOW_ID has not been measured, and if it does, end_action refuses to end it without ADVANCES (the safe direction). The vendor documents no status setter, and this package has none: a ticket's status follows its workflow (user guide, "Changing a ticket's status").

Assets and documents

from pathlib import Path
from easyvista_python_client import (
    EasyvistaClient,
    EasyvistaConfig,
    PostAsset,
    ev_contains_filter,
    ev_equals_filter,
)

with EasyvistaClient(EasyvistaConfig.from_env()) as client:
    asset = client.create_asset(PostAsset(catalog_id=3153, asset_tag="LAPTOP-001"))
    tag_filter = ev_equals_filter("ASSET_TAG", "LAPTOP-001")
    found = client.search_assets(search=tag_filter, max_rows=50)

    # On the instance this package was characterized against, `~` needs an
    # explicit wildcard to mean "contains" -- a bare value is exact match,
    # identical to `:`. ev_contains_filter appends it for you; the vendor
    # documents `~` as plain Contains, so pass wildcard=None if that is your
    # deployment. It raises ValueError if the value carries `_` or `[` (both
    # are metacharacters to `~` itself, with no escape) or `*`/`%` while a
    # wildcard is being appended. For an exact match on a tag like
    # "LAPTOP_01", use ev_equals_filter: `:` does not expand a wildcard.
    partial = client.search_assets(search=ev_contains_filter("ASSET_TAG", "LAPTOP"))

    # attach a file to a ticket (uploaded as base64 inside the JSON body)
    pdf = Path("report.pdf")
    client.add_document("I240101_0001", filename=pdf.name, content=pdf.read_bytes())
    attachments = client.list_documents("I240101_0001")

Usage (async)

import asyncio

from easyvista_python_client import AsyncEasyvistaClient, EasyvistaConfig


async def main():
    async with AsyncEasyvistaClient(EasyvistaConfig.from_env()) as client:
        ticket = await client.get_ticket("I240101_0001")
        print(ticket.rfc_number)


asyncio.run(main())

Configuration via environment

Set EASYVISTA_URL (or EASYVISTA_SERVER), EASYVISTA_ACCOUNT, and either EASYVISTA_TOKEN / EASYVISTA_TOKEN_FILE or EASYVISTA_LOGIN + EASYVISTA_PASSWORD, then call EasyvistaConfig.from_env().

Agent skills

skills/ holds Agent Skills for driving this client from an AI agent — one per domain (client setup, search syntax, tickets, actions, documents, assets, directory, reporting and context). Each is a directory with a SKILL.md following the Agent Skills specification; see skills/README.md for the index.

They are source-tree material: present in the git repository and the source distribution, absent from the installed wheel.

Contributing

See CONTRIBUTING.md for development setup and quality checks.

License

MIT — see LICENSE.

Sponsoring

The development of this package is indirectly supported by Novahé & Constellation.

Metadata

Release files for easyvista-python-client 0.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for easyvista-python-client 0.4.1
File Size Uploaded
easyvista_python_client-0.4.1.tar.gz 327.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for easyvista-python-client 0.4.1
File Interpreter ABI Platform
easyvista_python_client-0.4.1-py3-none-any.whl Python 3 none any Details

Total release size: 539.1 kB

Release files / easyvista_python_client-0.4.1.tar.gz

Download URL easyvista_python_client-0.4.1.tar.gz
Size 327.3 kB
Tags Source
SHA-256 checksum
How to use checksums
c69ccae3789a00c3b117ee3e8ad1e0721ed11c72969d1863f015b96159ac5134
BLAKE2b-256 checksum
How to use checksums
0de35ec567267958c8da2f72146272fcca99b16e26388df3c915a17064062e5b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release files / easyvista_python_client-0.4.1-py3-none-any.whl

Download URL easyvista_python_client-0.4.1-py3-none-any.whl
Size 211.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6e6687882bf93f48e4e47e5bda7c33b5a76a69d2f8dc952ce571f5e40e547d62
BLAKE2b-256 checksum
How to use checksums
9fe8d08e39396171b13ba7e3c9573591eecf320a8fc27749fbb9c5e9bd19eee4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.1 This release

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page