Modern Python SDK for Zendesk API
Project description
Modern Python SDK for Zendesk API, designed for automation and AI agents.
Table of Contents
- Why This SDK?
- Features
- Installation
- Quick Start
- Configuration
- API Methods
- Error Handling
- Proactive Rate Limiting
- Caching
- Examples
Why This SDK?
Zendesk has a powerful REST API, but using it directly is painful:
- Multiple API calls needed to get complete ticket context (ticket + comments + users)
- No type safety — just raw JSON dictionaries
- Manual pagination handling
- Boilerplate retry/rate-limit logic in every project
This SDK solves these problems with a clean, typed interface optimized for:
- Support automation — workflows, triggers, integrations
- Internal tools — dashboards, reports, bulk operations
- LLM agents — Claude Code, Codex, custom AI assistants that need structured Zendesk access
Developer Experience
- Predictable structure — typed Pydantic models instead of arbitrary dicts
- Complete context in one call —
get_enriched()returns ticket + all comments + all users - No boilerplate — pagination, caching, and object loading handled automatically
- Minimal API calls — built-in caching and batching reduce redundant requests
- Clear namespaces —
client.tickets.comments.add()is self-documenting
Features
- Type Safety: Full Pydantic v2 models for all Zendesk entities
- Namespace Pattern: Clean API —
client.users,client.tickets,client.help_center - Search: Raw queries + type-safe SearchQueryConfig, export methods for large datasets
- Pagination: Offset-based and cursor-based (export) with async iterators
- Caching: TTL-based caching for users, organizations, and Help Center
- Help Center: Full CRUD for Categories, Sections, and Articles
- Async HTTP: Built on httpx with retry logic, rate limiting, exponential backoff
- Proactive Rate Limiting: Monitors
X-Rate-Limit-Remainingand throttles before hitting limits - Authentication: Token auth (Basic Auth) and OAuth (Bearer token)
- Configuration: Environment variables or direct instantiation
Installation
pip install python-zendesk-sdk
Quick Start
import asyncio
from zendesk_sdk import ZendeskClient, ZendeskConfig
async def main():
config = ZendeskConfig(
subdomain="your-subdomain",
email="your-email@example.com",
token="your-api-token",
)
async with ZendeskClient(config) as client:
# Get a single user
user = await client.users.get(12345)
print(f"User: {user.name} ({user.email})")
# Get specific ticket
ticket = await client.tickets.get(12345)
print(f"Ticket: {ticket.subject}")
# Search tickets with pagination
async for ticket in client.search.tickets("status:open priority:high", limit=10):
print(f"High priority: {ticket.subject}")
asyncio.run(main())
Configuration
Token Authentication
config = ZendeskConfig(
subdomain="mycompany",
email="user@example.com",
token="api_token_here",
)
OAuth Authentication
config = ZendeskConfig(
subdomain="mycompany",
oauth_token="your_oauth_token",
)
Environment variables
# Token auth
export ZENDESK_SUBDOMAIN=mycompany
export ZENDESK_EMAIL=user@example.com
export ZENDESK_TOKEN=api_token_here
# Or OAuth
export ZENDESK_SUBDOMAIN=mycompany
export ZENDESK_OAUTH_TOKEN=your_oauth_token
config = ZendeskConfig() # Will load from environment
API Methods
Pagination
All list methods return Paginator objects. Three ways to work with them:
# 1. Get specific page
paginator = client.users.list(per_page=20)
users = await paginator.get_page(2) # Get page 2
# 2. Iterate through all items
async for user in client.users.list():
print(user.name)
# 3. Collect to list
users = await client.users.list(limit=50).collect()
# Get total count without iterating (uses Zendesk's count from response)
paginator = client.users.list()
total = await paginator.count() # async; issues a probe request if needed
cached = paginator.total_count # sync; None until at least one page fetched
Users
# Read
user = await client.users.get(user_id) # Get user by ID (cached)
user = await client.users.me() # Get current authenticated user
user = await client.users.by_email(email) # Find user by email (cached)
users = await client.users.get_many([id1, id2]) # Get multiple users (batch)
paginator = client.users.list() # List all users (paginator)
# Create
user = await client.users.create(
name="John Doe",
email="john@example.com",
role="end-user", # end-user, agent, admin
verified=True, # Skip email verification
organization_id=12345,
tags=["vip"],
user_fields={"department": "Sales"},
)
user = await client.users.create_or_update( # Upsert by email/external_id
name="John Doe",
email="john@example.com",
)
# Update
user = await client.users.update(
user_id,
phone="+1234567890",
tags=["premium"],
user_fields={"status": "active"},
)
# Delete
await client.users.delete(user_id) # Soft delete (recoverable 30 days)
await client.users.permanently_delete(user_id) # GDPR permanent deletion
# Suspension
user = await client.users.suspend(user_id) # Block user access
user = await client.users.unsuspend(user_id) # Restore user access
# Password (requires admin setting enabled)
await client.users.set_password(user_id, "NewPass123!")
reqs = await client.users.get_password_requirements(user_id)
# Merge duplicates
user = await client.users.merge(source_id, target_id) # Merge into target
Organizations
# Read
org = await client.organizations.get(org_id) # Get organization by ID (cached)
paginator = client.organizations.list() # List organizations (paginator)
# Create
org = await client.organizations.create(
name="Acme Corp",
domain_names=["acme.com"],
tags=["enterprise"],
organization_fields={"plan": "premium"},
)
org = await client.organizations.create_or_update( # Upsert by external_id
name="Acme Corp",
external_id="acme-123",
)
# Update
org = await client.organizations.update(
org_id,
name="Acme Corporation",
tags=["enterprise", "vip"],
organization_fields={"plan": "enterprise"},
)
# Delete
await client.organizations.delete(org_id)
Groups
# Read
group = await client.groups.get(group_id) # Get group by ID (cached)
count = await client.groups.count() # Get total number of groups
paginator = client.groups.list() # List all groups (paginator)
paginator = client.groups.list_assignable() # List assignable groups (paginator)
# Memberships — find which agents belong to a group
paginator = client.groups.list_memberships() # All memberships (paginator)
paginator = client.groups.list_group_members(group_id) # Members of specific group (paginator)
membership = await client.groups.get_membership(membership_id) # Get specific membership
# Example: get all agents in a group
members = await client.groups.list_group_members(group_id).collect()
for m in members:
user = await client.users.get(m.user_id)
print(f" {user.name} (default={m.default})")
# Create
group = await client.groups.create(
name="Support Team",
description="First-line support agents",
is_public=True,
)
# Update
group = await client.groups.update(
group_id,
name="Renamed Team",
description="Updated description",
is_public=False,
)
# Delete (soft delete)
await client.groups.delete(group_id)
Tickets
# Read
ticket = await client.tickets.get(ticket_id) # Get ticket by ID
tickets = await client.tickets.get_many([id1, id2]) # Get multiple tickets (batch)
paginator = client.tickets.list() # List tickets (paginator)
paginator = client.tickets.for_user(user_id) # User's tickets (paginator)
paginator = client.tickets.for_organization(org_id) # Org's tickets (paginator)
# Create
ticket = await client.tickets.create(
comment_body="Customer needs help with login",
subject="Login Issue",
priority="high", # low, normal, high, urgent
status="open", # new, open, pending, hold, solved
ticket_type="problem", # question, incident, problem, task
tags=["login", "urgent"],
)
# Update
ticket = await client.tickets.update(
ticket_id,
status="solved",
priority="normal",
comment={"body": "Issue resolved!", "public": True},
)
# Delete (moves to trash, recoverable for 30 days)
await client.tickets.delete(ticket_id)
Comments (nested under tickets)
paginator = client.tickets.comments.list(ticket_id) # List comments (paginator)
result = await client.tickets.comments.get_last(ticket_id) # Last comment + author
ticket = await client.tickets.comments.add(ticket_id, body, public=False)
await client.tickets.comments.make_private(ticket_id, comment_id)
comment = await client.tickets.comments.redact(ticket_id, comment_id, text)
# Get last comment with author (single API call)
result = await client.tickets.comments.get_last(ticket_id)
if result:
comment, author = result
print(f"{author.name}: {comment.body}")
Inline (in-body) images embedded in a comment are returned in comment.attachments by default (flagged with attachment.inline is True) — both via list/get_last and via enriched tickets.
Tags (nested under tickets)
tags = await client.tickets.tags.get(ticket_id) # Get tags
tags = await client.tickets.tags.add(ticket_id, ["vip"]) # Add tags
tags = await client.tickets.tags.set(ticket_id, ["new"]) # Replace all tags
tags = await client.tickets.tags.remove(ticket_id, ["old"]) # Remove tags
Ticket Fields
# Get all ticket fields (system + custom)
async for field in client.ticket_fields.list():
print(f"{field.title}: {field.type}")
# Get specific field by ID (cached)
field = await client.ticket_fields.get(field_id)
# Find field by title (case-insensitive)
field = await client.ticket_fields.get_by_title("Subscription")
Ticket Metrics
Read-only access to per-ticket metrics: first reply time, full resolution time, agent/requester wait time, on-hold time. Every time field exposes both calendar (wall-clock) and business (business-hours) values.
# Metrics for a specific ticket — primary use case
metrics = await client.ticket_metrics.for_ticket(ticket_id)
print(metrics.reply_time_in_minutes)
# {"calendar": 42, "business": 15}
# Metric by its own id
metrics = await client.ticket_metrics.get(metric_id)
# Iterate over all metrics (offset pagination)
async for m in client.ticket_metrics.list(per_page=100):
if m.full_resolution_time_in_minutes:
...
# Total count (single extra request, see Pagination)
total = await client.ticket_metrics.list().count()
Metrics are live data — the client intentionally does not cache responses.
Enriched Tickets
Load tickets with all related data (comments, users, organization, field definitions) in minimum API requests:
from zendesk_sdk import SearchQueryConfig
# Get single ticket with all related data
enriched = await client.tickets.get_enriched(12345)
# Batch: get multiple enriched tickets (much faster than calling get_enriched in a loop)
enriched_list = await client.tickets.get_many_enriched([12345, 12346, 12347])
print(f"Ticket: {enriched.ticket.subject}")
print(f"Requester: {enriched.requester.name}")
print(f"Assignee: {enriched.assignee.name if enriched.assignee else 'Unassigned'}")
# Organization is sideloaded with the ticket — no extra API call
if enriched.organization:
print(f"Organization: {enriched.organization.name}")
for comment in enriched.comments:
author = enriched.get_comment_author(comment)
print(f"Comment by {author.name}: {comment.body[:50]}...")
# Access custom field values with human-readable names
field_values = enriched.get_field_values()
print(f"Subscription: {field_values.get('Subscription')}")
# Or get specific field value by ID
value = enriched.get_field_value(360001234)
# Search with all data loaded (using SearchQueryConfig)
config = SearchQueryConfig.tickets(
status=["open"],
priority=["high", "urgent"],
organization_id=12345,
)
# search_enriched returns async iterator
async for item in client.tickets.search_enriched(config, limit=10):
print(f"{item.ticket.subject} - {len(item.comments)} comments")
Attachments
content = await client.attachments.download(content_url) # Download file
token = await client.attachments.upload(data, filename, content_type) # Upload file
# Attach to comment
await client.tickets.comments.add(ticket_id, "See attached", uploads=[token])
Search
All search methods return Paginator objects with the same interface as list methods.
Raw Queries (Zendesk syntax)
Use the same query syntax as in Zendesk UI — it just works:
# Tickets - iterate through results
async for ticket in client.search.tickets("status:open priority:high"):
print(ticket.subject)
# Users
async for user in client.search.users("role:admin"):
print(user.name)
# Organizations
async for org in client.search.organizations("tags:enterprise"):
print(org.name)
# Collect to list with limit
tickets = await client.search.tickets("status:pending", limit=100).collect()
# Search with enrichment (loads comments + users)
async for item in client.tickets.search_enriched("status:open", limit=10):
print(f"{item.ticket.subject} - {len(item.comments)} comments")
SearchQueryConfig (typed alternative)
Don't want to memorize Zendesk query syntax? Use SearchQueryConfig — your IDE will autocomplete available fields:
from zendesk_sdk import SearchQueryConfig
config = SearchQueryConfig.tickets(
status=["open", "pending"],
priority=["high", "urgent"],
organization_id=12345,
created_after=date(2024, 1, 1),
tags=["vip"],
exclude_tags=["spam"],
)
async for ticket in client.search.tickets(config):
print(ticket.subject)
config = SearchQueryConfig.users(
role=["admin", "agent"],
is_verified=True,
)
async for user in client.search.users(config):
print(user.name)
config = SearchQueryConfig.organizations(tags=["enterprise"])
async for org in client.search.organizations(config):
print(org.name)
Available SearchQueryConfig fields
| Field | Type | Description |
|---|---|---|
type |
SearchType | TICKET (default), USER, ORGANIZATION |
status |
List[str] | new, open, pending, hold, solved, closed |
priority |
List[str] | low, normal, high, urgent |
ticket_type |
List[str] | question, incident, problem, task |
organization_id |
int | Filter by organization |
requester_id |
int|"me"|"none" | Filter by requester |
assignee_id |
int|"me"|"none" | Filter by assignee |
group_id |
int | Filter by group |
tags |
List[str] | Include items with tags (OR) |
exclude_tags |
List[str] | Exclude items with tags |
created_after |
date | Created after date |
created_before |
date | Created before date |
updated_after |
date | Updated after date |
updated_before |
date | Updated before date |
via |
List[str] | Channel: email, web, chat, api, phone |
custom_fields |
Dict[int, Any] | Custom field values |
order_by |
str | Sort field |
sort |
str | asc or desc |
Export Search (No Zendesk Limit)
Regular search is capped at 1000 results by Zendesk. Export endpoint has no such limit:
# Export fetches ALL matching entities (no 1000 limit)
async for ticket in client.search.export_tickets("status:open"):
print(ticket.subject) # Will iterate through ALL open tickets
async for user in client.search.export_users():
print(user.name) # ALL users
async for org in client.search.export_organizations():
print(org.name) # ALL organizations
# Works with SearchQueryConfig too
config = SearchQueryConfig.tickets(status=["open"], priority=["high"])
async for ticket in client.search.export_tickets(config):
print(ticket.subject)
# With limit if you don't need everything
async for ticket in client.search.export_tickets("priority:high", limit=500):
print(ticket.subject)
| Method | Zendesk Limit | Pagination | Duplicates |
|---|---|---|---|
search.tickets() |
1000 max | Offset | Possible |
search.export_tickets() |
None | Cursor | None |
Views
Views are saved searches over tickets — the day-to-day workspace of an
agent. Read-only access through client.views: list views, fetch a
view's tickets, count tickets without loading them. Useful for
LLM-agents (skip query construction — use views the support team has
already curated) and dashboards (counts in one request).
# List views
async for view in client.views.list():
print(f"{view.title} (active={view.active})")
# Active views only
active = await client.views.list(active_only=True).collect()
# Get a single view (cached)
view = await client.views.get(12345)
print(view.conditions) # raw {'all': [...], 'any': [...]}
# Get many views in one request (max 100 IDs)
views = await client.views.get_many([1, 2, 3])
# Iterate tickets in a view
async for ticket in client.views.tickets(12345):
print(f"#{ticket.id}: {ticket.subject}")
# Count tickets without loading them
count = await client.views.count(12345)
print(f"{count.value} tickets (fresh={count.fresh})")
# Counts for several views in one request (great for dashboards, max 20 IDs)
counts = await client.views.count_many([1, 2, 3])
for c in counts:
print(f"view {c.view_id}: {c.value}")
Read-only by design. Views are owned by admins and rarely edited via API. For very large views the count is served from a server-side cache — check
ViewCount.freshto know if it's authoritative.
Help Center
Access Help Center (Guide) via client.help_center namespace:
Categories
cat = await client.help_center.categories.get(category_id)
paginator = client.help_center.categories.list() # Paginator
cat = await client.help_center.categories.create(name, description=description)
cat = await client.help_center.categories.update(category_id, name=new_name)
await client.help_center.categories.delete(category_id, force=True)
Sections
sec = await client.help_center.sections.get(section_id)
paginator = client.help_center.sections.list() # Paginator
paginator = client.help_center.sections.for_category(category_id)
sec = await client.help_center.sections.create(category_id, name, description=description)
sec = await client.help_center.sections.update(section_id, name=new_name)
await client.help_center.sections.delete(section_id, force=True)
Articles
art = await client.help_center.articles.get(article_id)
paginator = client.help_center.articles.list() # Paginator
paginator = client.help_center.articles.for_section(section_id)
paginator = client.help_center.articles.for_category(category_id)
results = await client.help_center.articles.search(query)
art = await client.help_center.articles.create(section_id, title, body=html)
art = await client.help_center.articles.update(article_id, title=new_title)
await client.help_center.articles.delete(article_id)
Example
async with ZendeskClient(config) as client:
hc = client.help_center
# Get permission_group_id from existing article (required for article creation)
existing = await hc.articles.list(per_page=1).get_page()
article_details = await hc.articles.get(existing[0].id)
permission_group_id = article_details.permission_group_id
# Create category -> section -> article hierarchy
category = await hc.categories.create(
name="Product Documentation",
description="Help articles for our product"
)
section = await hc.sections.create(
category.id,
"Getting Started"
)
article = await hc.articles.create(
section.id,
title="Installation Guide",
body="<h1>Installation</h1><p>Follow these steps...</p>",
permission_group_id=permission_group_id,
draft=True,
label_names=["installation", "guide"],
)
# Search articles (useful for AI assistants)
results = await hc.articles.search("password reset")
for article in results:
print(f"{article.title}")
print(f"Snippet: {article.snippet}") # Matching text with <em> tags
# Cascade delete (removes category + all sections + all articles)
await hc.categories.delete(category.id, force=True)
Note:
delete()for categories and sections requiresforce=Trueas a safety measure since they cascade delete all child content.
Error Handling
The SDK provides specific exception classes for different error types:
from zendesk_sdk.exceptions import (
ZendeskAuthException,
ZendeskHTTPException,
ZendeskPaginationException,
ZendeskRateLimitException,
ZendeskTimeoutException,
ZendeskValidationException,
)
async with ZendeskClient(config) as client:
try:
user = await client.users.get(12345)
except ZendeskAuthException as e:
# 401/403 - Authentication failed
print(f"Auth error: {e.message}")
except ZendeskRateLimitException as e:
# 429 - Rate limit exceeded
print(f"Rate limited, retry after: {e.retry_after}s")
except ZendeskHTTPException as e:
# Other HTTP errors (404, 500, etc.)
print(f"HTTP {e.status_code}: {e.message}")
except ZendeskTimeoutException as e:
# Request timeout
print(f"Timeout: {e.message}")
except ZendeskPaginationException as e:
# Pagination errors (e.g., Zendesk 1000 result limit)
print(f"Pagination error: {e.message}")
Automatic Retry
The SDK automatically retries on:
- Rate limiting (429) - with respect to
Retry-Afterheader - Server errors (5xx) - with exponential backoff
- Network errors and timeouts
Configure retry behavior:
config = ZendeskConfig(
subdomain="mycompany",
email="user@example.com",
token="api_token",
timeout=30.0, # Request timeout in seconds
max_retries=3, # Number of retry attempts
)
Proactive Rate Limiting
By default, the SDK reacts to rate limiting after hitting a 429 response. With proactive rate limiting, the SDK reads the X-Rate-Limit-Remaining header from every response and starts throttling before hitting the limit:
config = ZendeskConfig(
subdomain="mycompany",
email="user@example.com",
token="api_token",
proactive_ratelimit=50, # Start throttling when < 50 requests remaining
proactive_ratelimit_request_interval=10, # Wait 10s between requests when throttling
)
When X-Rate-Limit-Remaining drops below the threshold, the SDK pauses between requests to let the quota recover. Once remaining goes back above the threshold, requests resume at full speed.
| Parameter | Default | Description |
|---|---|---|
proactive_ratelimit |
None (disabled) | Threshold to start throttling |
proactive_ratelimit_request_interval |
10 | Seconds to wait between requests when throttling |
Zendesk typically allows 400 requests per minute. A threshold of 40-60 provides a good safety margin.
Caching
The SDK includes built-in caching for frequently accessed resources. Caching is enabled by default and can be configured or disabled.
Default Cache Settings
| Resource | TTL | Max Size |
|---|---|---|
| Users | 5 min | 1000 |
| Organizations | 10 min | 500 |
| Groups | 10 min | 500 |
| Ticket Fields | 30 min | 200 |
| Articles | 15 min | 500 |
| Categories | 30 min | 200 |
| Sections | 30 min | 200 |
Custom Cache Configuration
from zendesk_sdk import CacheConfig, ZendeskClient, ZendeskConfig
config = ZendeskConfig(
subdomain="mycompany",
email="user@example.com",
token="api_token",
cache=CacheConfig(
enabled=True,
user_ttl=60, # 1 minute
user_maxsize=100,
org_ttl=300, # 5 minutes
article_ttl=600, # 10 minutes
)
)
Disable Caching
config = ZendeskConfig(
subdomain="mycompany",
email="user@example.com",
token="api_token",
cache=CacheConfig(enabled=False)
)
Cache Control
# Check cache statistics
info = client.users.get.cache_info()
print(f"Hits: {info.hits}, Misses: {info.misses}")
# Clear all cached users
client.users.get.cache_clear()
# Invalidate specific entry
client.users.get.cache_invalidate(user_id)
Examples
See the examples/ directory for complete usage examples:
basic_usage.py- Basic configuration and API operationsusers.py- Users CRUD (create, update, delete, suspend, passwords)organizations.py- Organizations CRUD (create, update, delete, upsert)groups.py- Groups CRUD (create, update, delete, list)search.py- Type-safe search with SearchQueryConfigpagination_example.py- Working with paginated resultserror_handling.py- Error handling patternsenriched_tickets.py- Loading tickets with related datahelp_center.py- Help Center categories, sections, and articlescaching.py- Cache configuration and usage
Requirements
- Python 3.8+
- httpx
- pydantic >=2.0
- async-lru
License
MIT License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file python_zendesk_sdk-0.16.2.tar.gz.
File metadata
- Download URL: python_zendesk_sdk-0.16.2.tar.gz
- Upload date:
- Size: 111.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e96fb5763f94989b83065af575c00054d086c7014c7d932db56d41eaf0bf3b1
|
|
| MD5 |
bf80a24b6cd2b99a9b8daa855f1165ad
|
|
| BLAKE2b-256 |
b84ee7c6cbf494e9ff360acf99912065c0f45b3c85ac7d0485390d2004ca8ed8
|
Provenance
The following attestation bundles were made for python_zendesk_sdk-0.16.2.tar.gz:
Publisher:
publish.yml on bormog/python-zendesk-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
python_zendesk_sdk-0.16.2.tar.gz -
Subject digest:
0e96fb5763f94989b83065af575c00054d086c7014c7d932db56d41eaf0bf3b1 - Sigstore transparency entry: 2153476024
- Sigstore integration time:
-
Permalink:
bormog/python-zendesk-sdk@b7b30e9899a35c5d5a3cccde955378ed839c8f0a -
Branch / Tag:
refs/tags/v0.16.2 - Owner: https://github.com/bormog
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b7b30e9899a35c5d5a3cccde955378ed839c8f0a -
Trigger Event:
release
-
Statement type:
File details
Details for the file python_zendesk_sdk-0.16.2-py3-none-any.whl.
File metadata
- Download URL: python_zendesk_sdk-0.16.2-py3-none-any.whl
- Upload date:
- Size: 83.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b29189e5c4624d9d76ba7019ce52307661ba86908dd20df52b88a4e1f221b5bf
|
|
| MD5 |
1c79e01ed391ef33a9ebe04b011b6ed9
|
|
| BLAKE2b-256 |
6e376b7582bffeac7e84819b1d68e6722e34d29df06dde400306a808da868641
|
Provenance
The following attestation bundles were made for python_zendesk_sdk-0.16.2-py3-none-any.whl:
Publisher:
publish.yml on bormog/python-zendesk-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
python_zendesk_sdk-0.16.2-py3-none-any.whl -
Subject digest:
b29189e5c4624d9d76ba7019ce52307661ba86908dd20df52b88a4e1f221b5bf - Sigstore transparency entry: 2153476820
- Sigstore integration time:
-
Permalink:
bormog/python-zendesk-sdk@b7b30e9899a35c5d5a3cccde955378ed839c8f0a -
Branch / Tag:
refs/tags/v0.16.2 - Owner: https://github.com/bormog
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b7b30e9899a35c5d5a3cccde955378ed839c8f0a -
Trigger Event:
release
-
Statement type: