Skip to main content

csa-zendesk

project_tracker_base: CINO Project Tracker:appf7fRQUvY9Iy7sL
project_tracker_table: Projects:tblchmbxSAavvJKaY
project_tracker_record: csa-zendesk:recvtmgqPccgLvuXz
project_source: github:CloudSecurityAlliance-Internal/CINO-Projects/projects/CloudSecurityAlliance/csa-zendesk

A Python library and local stdio MCP server over the Zendesk REST API, targeting 100% API coverage.

Status: Block 0 (foundations), Block 0b (OAuth), Block 0e (a read-only MCP server) and Block 1 (the write surface and attachments) are complete. src/ holds the typed error hierarchy, the error parser, the pagination guard, a transport with OAuth end to end (connect()), the Backend seam with an offline FakeBackend, the fail-closed capability policy, and a thin ZendeskClient.

What exists: csa-zendesk-mcp, a stdio MCP server at rung E2 — "work tickets for real:

  • note, write" — see Using the MCP server below. Ten tools: four reads (get_ticket, search_tickets, list_comments, get_attachment), six writes (update_ticket, assign_ticket, add_internal_note, solve_ticket, upload_file, delete_upload), plus three auth-lifecycle tools (authenticate, auth_status, logout) that sit outside the capability model by design (ADR-017) so a user never has to leave the session to sign in or out. reply_publicly, merge_tickets and close_ticket are deliberately not registered — see "What does not exist" below.

What has not been verified: any write, against a live ticket. The read path — OAuth, the MCP handshake, the allowlist failing closed, ticket text wrapped as untrusted data — was confirmed live against the real Zendesk tenant on 2026-09-21, from a separate, unmodified clone at 0.1.0. No tool in the write surface (update_ticket, assign_ticket, add_internal_note, solve_ticket, upload_file, delete_upload) has been exercised against live Zendesk — Block 1 was built and reviewed entirely offline, against FakeBackend and mocked transports. The live end-to-end write walkthrough is TODO.md F1 and happens after this branch merges, with a human present; do not read "Block 1 is complete" as "Block 1 is live-verified" — they are separate claims.

What does not exist: reach, admin, and the rest of the B1–B5 track. reply_publicly (public reply) and merge_tickets both email a requester and are held for rung E5 (CSA_ZD_ALLOW_REACH); close_ticket is terminal (analysis/API-SURFACE.md §5.4d — Zendesk accepts status: "closed" and never lets it change again) and is held back deliberately, not because it is unbuilt. No admin capability (ADMIN_READ/ADMIN_WRITE) is granted. Of the 54 tools in the whole-project design, ten are built; the rest is still to come. Do not describe any tool beyond those ten as working: the Scope table below is the coverage target this project is building toward, not the built surface.

Scope

Capability Operations 1.0.0
Ticketing 640 yes
Help Center 182 yes
Status 3 yes
Voice (Talk) 60 post-1.0
Live Chat, Messaging, AI Agents, Custom Data, Sales CRM no published spec post-1.0

822 of 882 machine-readable operations at 1.0.0 — which is coverage of the published specs, not of the API. The 60 deferred are all Voice. Status's three operations have no published spec and are not in the inventory at all, so they sit outside both figures — which is what made the earlier count wrong: 825 counted them in a numerator measured against a denominator that excludes them. Guarded by scripts/check_counts.py. The Help Center spec describes roughly 18 of ~30 documented families; about a dozen, several of them live, have no spec entry and need hand-written methods. See analysis/API-SURFACE.md §4b.

Why this exists

The third-party Zendesk MCP servers are thin. The most popular — 115★, actively maintained, genuinely well built — ships 7 tools. The largest ships ~47. Against 882 operations.

More consequentially, the whole field shares four weaknesses:

  • No capability policy. OAuth scopes, fixed at client registration, are the only control. Zendesk has 54 granular scopes, which gives a local fail-closed policy layer something real to bind to.
  • Offset pagination everywhere. Zendesk caps offset paging at 100 pages / 10,000 records and then returns HTTP 400. Large result sets truncate, and the tools present the truncation as an answer.
  • API-token auth. Zendesk permanently deactivates all API tokens on 2027-04-30, and no account can create a new one after 2026-10-27.
  • Pre-2.0 MCP SDK. Hand-rolled list_tools/call_tool dispatch means no structured output and no tool annotations, so a client cannot tell a read from a destructive write.

Architecture

Third in the line after csa-skilljar and csa-google-workspace, on the same spine:

Backend (Protocol)   the seam - keyword-only args, returns raw upstream envelopes
    ^ wrapped by
PolicyBackend        capability gating; FAILS CLOSED - an ungated method is refused
    ^ consumed by
ZendeskClient        thin typed library surface (the public product)
    ^ consumed by
mcp/_tools/*.py      per-family register_*(app, get_client) producers

mcp/_tools/*.py is the target layout for the full 54-tool surface, not what exists today: csa-zendesk-mcp (src/csa_zendesk/server.py) registers its thirteen tools flat, with no mcp/ package and no per-family producer modules yet.

Enforcement lives in the wrapper around the seam, not in the tools, so a library embedder gets the same guarantee an MCP client does.

What is here

Path What
specs/ Three upstream OpenAPI snapshots + PROVENANCE.md (URLs, sha256)
analysis/API-SURFACE.md Start here. The enumeration and the probe findings
analysis/operation-inventory.csv 882 rows, one per operation
analysis/family-probe.json 49 live availability probes
scripts/inventory.py Regenerates the inventory from specs/
scripts/probe_families.py Re-runs the live probes (GET only)
scripts/probe_access.py Access audit: what the credential actually reaches
docs/superpowers/specs/ The design. Start here before proposing anything
TODO.md The index of all open work. Start here for what is unfinished
DECISIONS-ADR.md Decision log index; entries in DECISIONS-ADR/
WAITING-FOR.md Conditions with observable triggers; entries in WAITING-FOR/

Zendesk publishes the OpenAPI specs but links to none of them; all three were found by probing URL shapes. They are snapshots of someone else's moving target — re-fetch and diff before trusting them.

Findings that constrain the design

Six are recorded in analysis/API-SURFACE.md. The three that change how the code must be written:

  1. users/me.json returns HTTP 200 with "name": "Anonymous user" when wholly unauthenticated. It serves anonymous Help Center visitors, so it degrades instead of refusing — and it is the endpoint every client uses as a credential health check. Validating against it reports healthy for a missing credential. Credential validation probes a resource endpoint and asserts a non-null user.id.
  2. Search takes offset pagination only and stops at 1000 results, while reporting a count of six figures. A tool that surfaces count as if the caller could page to it is lying. search/export is the cursor-paginated, uncapped alternative.
  3. Pagination cannot be generated from the specs. The Help Center spec declares paging on 0 of 96 GET operations, yet cursor paging demonstrably works. The generator must not read silence as "unpaginated".

Deliberate exclusions

Six families — 47 operations, 5% of the surface — are out of scope and will not be built: IT Asset Management, Group SLA Policies, Workspaces, Ticket Form Statuses, Audit Logs, and Help Center Service Catalog Items. The development account cannot reach them (403: a plan boundary), so they cannot be tested, and this project does not ship API code it has never called. See ADR-001 for the reasoning and WAITING-FOR-001 for what would reopen it.

This does not relax the error layer: plan boundaries differ per account, so any deployment can meet a 403 on an endpoint we did implement, and the taxonomy must say "your plan does not include this" rather than "this is broken".

Configuration

The library authenticates by OAuth and by nothing else (ADR-015). HttpClient takes a token_provider callable and sends a Bearer header; there is no API-token code path, no fallback, and no environment variable the library reads. A fallback that silently activates when OAuth is misconfigured turns an auth failure into something that reads like a permissions failure, which is the confusion the 401 handling goes out of its way to prevent.

OAuth client

Registered in Zendesk Admin Center (Apps and integrations › APIs › OAuth clients) as csa-zendesk, 2026-09-18. Redirect URIs are the three loopback candidates the callback listener binds, in order; the client's scope list is a ceiling, and what a token actually receives is whatever CSA_ZENDESK_SCOPES requests within it.

Local variable Zendesk's own label What it is
CSA_ZENDESK_SUBDOMAIN Subdomain the <subdomain> in https://<subdomain>.zendesk.com, with no scheme and no suffix
CSA_ZENDESK_MCP_SERVER_IDENTIFIER Identifier the OAuth client_id — csa-zendesk. Not a secret
CSA_ZENDESK_MCP_SERVER_SECRET Secret issued to every client regardless of kind. Retained, unused — see API-SURFACE §7.3
CSA_ZENDESK_SCOPES scope (request) space-separated, defaults to read. Must be a subset of the ceiling
CSA_ZENDESK_TOKEN_FILE — override for the 0600 token file (ADR-009)

Why the names are long. They are local names, not vendor names, and they are explicit on purpose: one machine runs many CSA projects against many vendors, so a variable has to say which project, which role, and which vendor without context. Where a name maps to something an operator reads off a vendor screen, it takes the vendor's own label for the last segment — Zendesk calls the client id the Identifier, so the variable does too, and nobody has to translate while looking at the form.

Registered ceiling: read tickets:write ticket_attachments:write ticket_views:write triggers:write (corrected 2026-09-21 — a live screenshot showed triggers:write was already on the client and missing from this line; see analysis/API-SURFACE.md §7.2b). triggers:write is not requested by anything this project ships at rung E2; it sits on the ceiling unused until an admin-configuration tool needs it. impersonate is deliberately absent — it is the one scope that would break the invariant that this tool can do nothing in Zendesk that its operator could not already do.

Getting a token

csa-zendesk (src/csa_zendesk/cli.py) is a small console script, a door into OAuth rather than a product: auth login runs the flow once and persists the result — opening a browser, or printing a URL to paste back with --paste on a remote shell with no browser of its own — auth status reports whether a token file exists, its path, its expiry and its granted scope without a network call, auth whoami confirms live which Zendesk identity it resolves to, and auth logout revokes the stored token server-side and then clears the local file. All four print human-facing text to stderr except whoami's and status's own answer, which goes to stdout since either might reasonably be piped; none of the four can print the token itself.

Token lifetimes are requested at their documented maxima, on every login and every refresh: expires_in at 172,800 seconds (2 days) and refresh_token_expires_in at 7,776,000 seconds (90 days) — the ceilings Zendesk's OAuth token endpoint documents, not arbitrary choices (see _flow.MAX_ACCESS_TOKEN_LIFETIME_SECONDS / MAX_REFRESH_TOKEN_LIFETIME_SECONDS). Both fields are resent on every refresh, not just at login, because Zendesk rotates the refresh token on every use (single-use, confirmed against the live tenant): re-requesting the maximum each time makes the 90-day window slide forward instead of shrinking back to Zendesk's 30-day default on first refresh. This is deliberately paired with auth logout: both tokens already live in the same 0600 file, so a short access-token lifetime buys nothing against file theft while costing a refresh every 30 minutes instead — maximising lifetimes without a real revoke path would be careless (TODO.md E11, E15). auth logout revokes the access token via DELETE /api/v2/oauth/tokens/current; this also invalidates the paired refresh token — not stated by Zendesk's API spec, but confirmed 2026-09-19 against the live tenant (TODO.md E20, analysis/API-SURFACE.md §7.4). This is what makes the maximal lifetimes above defensible: a stolen token file does not survive a logout.

The research scripts under scripts/ — zd.py, ui_actions.py, probe_families.py, probe_access.py — which refresh analysis/ and ship in no package, authenticate the same way as everything else: OAuth, through the token file above (ADR-009), using the same CSA_ZENDESK_SUBDOMAIN and CSA_ZENDESK_MCP_SERVER_IDENTIFIER variables. ./.env is not a credential source for anything in this repo (ADR-015) — the interim API-token path (CINO_CSA_ZENDESK + CINO_CSA_ZENDESK_EMAIL, basic auth as EMAIL/token:TOKEN) was removed once the scripts were ported off it. An operator's old token may still physically sit in a local ./.env; nothing here reads it, and removing it is the operator's own call.

export CSA_ZENDESK_SUBDOMAIN=<subdomain>
export CSA_ZENDESK_MCP_SERVER_IDENTIFIER=<client-id>
csa-zendesk auth login               # once, per operator - opens a browser
python3 scripts/inventory.py         # 882 operations
python3 scripts/probe_families.py    # 43/49 families reachable (as last measured, under the API-token path)

Using the MCP server

This rung is E2 — "work tickets for real: + note, write." csa-zendesk-mcp (the console script src/csa_zendesk/server.py registers) exposes thirteen tools — ten that touch ticket data, plus the three auth-lifecycle tools (authenticate, auth_status, logout). The ten: four reads — get_ticket, search_tickets, list_comments, get_attachment — and six writes — update_ticket, assign_ticket, add_internal_note, solve_ticket, upload_file, delete_upload — and connects with TICKET_READ, TICKET_WRITE, TICKET_NOTE, TICKET_SOLVE and TICKET_ATTACH (server.E2_CAPABILITIES), nothing more. reply_publicly, merge_tickets and close_ticket are not registered at all — a tool the model can see but must not use is worse than one that is simply absent — so no capability grant here can reach them. policy.py's gate refuses any capability E2_CAPABILITIES does not grant, independent of what the tool table lists.

get_attachment is a read, not a write, even though it is documented here and not above: it gates on TICKET_READ, the same capability rung E1 already grants, so it works at E1 too — reading a ticket's attachments is no more privileged than reading the ticket itself. It is also scoped by neither allowlist: an attachment is named by attachment_id, not ticket_id, so there is no ticket for CSA_ZD_ALLOWLIST_READ to check against — only the capability gates it.

The server extra is not installed by default — the library itself has no dependency on the MCP SDK, so a consumer who only wants the typed ZendeskClient never pulls it in:

pip install -e '.[server]'

E2 needs two things E1 did not, and neither is guessable

1. The token must carry write scope, and the client must be re-authenticated. A token minted for E1 requested only read. Every one of the six write tools calls a PUT/POST/ DELETE endpoint, and a read-scoped token fails all of them with a plain 403 — which reads exactly like a permissions misconfiguration, not like "this token was never asked for write." Widen CSA_ZENDESK_SCOPES before re-running login:

export CSA_ZENDESK_SCOPES='read tickets:write ticket_attachments:write'
csa-zendesk auth login   # re-run — a wider scope only takes effect on a fresh grant

tickets:write covers update_ticket/assign_ticket/add_internal_note/solve_ticket (all PUT /api/v2/tickets/{id}, constrained per-tool by tools.py, not by the scope); ticket_attachments:write covers upload_file/delete_upload (/api/v2/uploads). Both are within the OAuth client's registered ceiling (read tickets:write ticket_attachments:write ticket_views:write triggers:write — see OAuth client above); requesting anything outside that ceiling fails closed with 400 invalid_scope at login, not silently. auth_status reports the token's granted scope with no network call — check it after re-running login if a write still 403s.

2. CSA_ZD_ALLOWLIST_WRITE must name the ticket ids writes may touch, and unset permits nothing. Exactly the same shape as CSA_ZD_ALLOWLIST_READ at E1, and the same failure mode that was a Critical finding in Block 0e: unset does not mean unrestricted, it means every write is refused with a PolicyError and no clue why, since the token, the capability grant and the tool registration are all otherwise correct. Set it explicitly — * to permit every ticket, or a comma-separated list of ticket ids to scope this install narrowly (the safer default for a write-capable install, unlike the read side's usual *-for-triage posture):

export CSA_ZD_ALLOWLIST_WRITE='<ticket-id>,<ticket-id>'

upload_file, delete_upload and get_attachment are not scoped by either allowlist — none of the three takes a ticket_id (an upload is not yet attached to any ticket; an attachment is named by its own id) — so CSA_ZD_ALLOWLIST_WRITE governs exactly the other four write tools, the ones that act on a named ticket.

Know what that means for get_attachment before you rely on the allowlist. An install pinned to one ticket can still read the content of any attachment in the tenant, because an attachment_id does not say which ticket it belongs to and this server does not go looking. Attachments are where the sensitive material usually is, so this is the one place the allowlist does not deliver what it otherwise does. It is not a hole in a security boundary — the real boundary is the OAuth token's own scope, and anyone holding this credential could open the same attachment in the Zendesk UI by hand — but it is a hole in the blast-radius narrowing that is the whole reason to set an allowlist. There is no setting that turns this one tool off — E2_CAPABILITIES is fixed in the code — so if it matters for your install, the only remedy available today is not to grant this server the credential. TODO.md G3 tracks the decision about scoping it properly.

One further thing, which changes the size of this rather than its shape: get_attachment returns a content_url, and Zendesk attachment content URLs are fetchable without authentication unless the tenant has enabled "require authentication to download attachments" (it is off by default). So the exposure is not only "whoever holds this credential can read any attachment" — it is that the server emits a URL anything else with sight of the model's context can fetch, outside the credential entirely. Check that tenant setting before relying on this rung.

CSA_ZD_ALLOWLIST_READ is still not optional (unchanged from E1): unset means nothing is permitted for get_ticket/list_comments, even though search_tickets and get_attachment (neither carries a subject_var) work regardless. Set both allowlists explicitly rather than relying on this asymmetry.

Then register the server with Claude Code. The registration name is csa-zendesk — a different namespace from the executable, matching the rest of this fleet (csa-google-workspace, csa-skilljar, customer360, firecrawl — none carries an -mcp suffix) — and it is what prefixes every tool the model sees, so get_ticket shows up as mcp__csa-zendesk__get_ticket. -s user registers it for every session rather than binding it to one project directory — without it (the default, local scope), running this from inside a git worktree resolves to the worktree's parent repository, so the server registers against a path you didn't type and never shows up in the session you're working in:

claude mcp add csa-zendesk -s user \
  -e CSA_ZENDESK_SUBDOMAIN=<subdomain> \
  -e CSA_ZENDESK_MCP_SERVER_IDENTIFIER=<client-id> \
  -e CSA_ZD_ALLOWLIST_READ='*' \
  -e CSA_ZD_ALLOWLIST_WRITE='<ticket-id>,<ticket-id>' \
  -- /abs/path/to/csa-zendesk/.venv/bin/csa-zendesk-mcp

Use an absolute path to the installed csa-zendesk-mcp executable, not the bare command name — from a source checkout it lives in that checkout's own venv, and a bare name resolves through PATH, which may find a different install or none at all. The equivalent claude_desktop_config.json stanza (the JSON key is the registration name, csa-zendesk, not the executable):

{
  "mcpServers": {
    "csa-zendesk": {
      "command": "/abs/path/to/csa-zendesk/.venv/bin/csa-zendesk-mcp",
      "env": {
        "CSA_ZENDESK_SUBDOMAIN": "<subdomain>",
        "CSA_ZENDESK_MCP_SERVER_IDENTIFIER": "<client-id>",
        "CSA_ZD_ALLOWLIST_READ": "*",
        "CSA_ZD_ALLOWLIST_WRITE": "<ticket-id>,<ticket-id>"
      }
    }
  }
}

CSA_ZENDESK_SCOPES (see the OAuth client table above) is read at authenticate time — _cmd_authenticate, defaulting to read — which is why widening it and re-running auth login (above) is a step of its own, not something this registration triggers on its own.

There is no separate login step to run first. authenticate, auth_status and logout are themselves tools, reachable from inside the session at every rung — including before this one has a working credential — so a user who is logged out, or whose credential has lapsed, never has to leave Claude Code to fix it: the server's own instructions tell the model to call authenticate the moment another tool reports it is not authorized. logout sits alongside them rather than being left to the CLI, per ADR-017 — a surface that can acquire a credential must also expose a way to relinquish it, reachable at least as easily as the tool that acquires it.

Verify the install worked before relying on it: ask the model to call auth_status (confirms a token is on disk, with its expiry and granted scope, no network call — check the scope here first if a write is about to 403), then get_ticket on a ticket id you know exists. A PolicyError naming CSA_ZD_ALLOWLIST_READ or CSA_ZD_ALLOWLIST_WRITE at that step, or at a write, means the corresponding allowlist above is still unset or too narrow — set it and retry the same call before assuming anything else is wrong. A plain 403 on a write (not a PolicyError) means the token itself lacks the scope — re-check auth_status's reported scope against the two above.

Uploading and attaching a file — two steps, not one

upload_file alone does not put a file on a ticket. It sends the file's bytes to Zendesk and gets back a token naming bytes that exist on Zendesk's side attached to nothing — invisible everywhere else in this server's surface, including get_ticket and list_comments on the ticket you meant to attach it to. Calling only upload_file and stopping looks like it did nothing, because from the ticket's point of view it did. The file becomes visible on a ticket only on a second call, add_internal_note(ticket_id=..., uploads=[token]), which carries the token onto the ticket as a comment attachment. delete_upload(token=...) exists specifically to clean up a token from a failed or abandoned first step — an unattached upload is litter nothing else in this surface will ever show you, so without a deliberate delete_upload call it stays on Zendesk's side indefinitely.

upload_file's bytes travel over MCP as content_base64 — a base64-encoded string, not raw bytes or a file path — because MCP tool arguments are JSON, which has no binary type. Decoding uses validate=True, so malformed base64 is refused with an error rather than silently decoding to truncated or empty bytes. upload_file also refuses empty content outright, which is not the ordinary empty-write refusal every other write tool has: Zendesk accepts a zero-byte upload and hands back a real, usable-looking token for it, so without this refusal the failure would be silent — a token that names an attachment which downloads as nothing.

Development

python3 -m venv .venv
./.venv/bin/pip install -e '.[dev,server]'

server is an optional extra ([project.optional-dependencies]), not a hard dependency — the library stays importable without the MCP SDK. Install it anyway in a dev environment: it backs src/csa_zendesk/server.py (the csa-zendesk-mcp console script), and tests/test_public_api.py's import-time stdout guard imports every module in the package, server.py included, so the test suite fails to collect without it.

./.venv/bin/pytest --cov=csa_zendesk --cov-fail-under=100 -q
./.venv/bin/ruff check .
./.venv/bin/ruff format --check src tests
./.venv/bin/mypy --strict src
python3 scripts/check_public_safe.py
python3 scripts/check_boundaries.py

ruff check . lints the whole tree, not a directory list. ruff check src tests scripts once left tracked-but-unnamed experiments/ unlinted, hiding a NameError in all three scripts that write to a live ticket. Anything that needs to be exempted from lint or format is an exclusion in pyproject.toml, where it is reviewable — not an absence from the command line. check_boundaries.py fails if a tool is not bucket-pure: every operation shared by more than one tool must carry a real constraint distinguishing them (ADR-016).

License

Apache 2.0.

Release files for csa-zendesk 0.2.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 csa-zendesk 0.2.0
File Size Uploaded
csa_zendesk-0.2.0.tar.gz 201.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for csa-zendesk 0.2.0
File Interpreter ABI Platform
csa_zendesk-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 330.6 kB

Release files / csa_zendesk-0.2.0.tar.gz

Download URL csa_zendesk-0.2.0.tar.gz
Size 201.3 kB
Tags Source
SHA-256 checksum
How to use checksums
4121cb14c4b64422f136cb3b312ddcca19bd83e48c0918133e8abb447cd7624c
BLAKE2b-256 checksum
How to use checksums
a57d07afb88cdc1c433b0bfaaab9209e5826ee65bacff707b76c6cd1bcd094ac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Sep 22, 2026.

Transparency log

Release files / csa_zendesk-0.2.0-py3-none-any.whl

Download URL csa_zendesk-0.2.0-py3-none-any.whl
Size 129.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7d5e690cc38053c9ff71d171cca50fcc2b3454322edc942175401d0fc08b0a1d
BLAKE2b-256 checksum
How to use checksums
dd6c7f678c946a585bccbf71d22189af8b576132492d19f01b78510e66597447
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Sep 22, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

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