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.0

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.0
File Size Uploaded
easyvista_python_client-0.4.0.tar.gz 325.3 kB Details

Built distribution (wheel)

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

Total release size: 536.6 kB

Release files / easyvista_python_client-0.4.0.tar.gz

Download URL easyvista_python_client-0.4.0.tar.gz
Size 325.3 kB
Tags Source
SHA-256 checksum
How to use checksums
a33bbb956ffc035190eec7355b6f131e5628fdcd7b8dd757dded4e6d10c4ee0d
BLAKE2b-256 checksum
How to use checksums
ff34f486c3700f7543b6158dc6e82ad2ba60d4617d41d7cc719db79e3fe2edae
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 2, 2026.

Transparency log

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

Download URL easyvista_python_client-0.4.0-py3-none-any.whl
Size 211.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
937e54d405814c667f5f741484829c9db9db91745e86a101f59f5ddbfab99db5
BLAKE2b-256 checksum
How to use checksums
77fa9976ca6fd49bacde14753ef32b264a186dfdd658632c699d47414caaa3fb
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 2, 2026.

Transparency log

Release history Release notifications | RSS feed

0.4.1

2 release files

This release

0.4.0 This release

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