StackShift Python SDK
Official Python SDK for StackShift.
Install
pip install stackshift
Partner platforms
Staff-enabled partner accounts buy domains for merchants from their StackShift wallet. Use an API token with the partner:manage scope (and mail:manage to run campaigns) on your server:
domain = stackshift.partner.purchase_domain(
domain="merchantshop.com",
years=1,
merchant_ref="m_123",
registrant={"first_name": "Ada", "last_name": "Obi", "email": "ada@merchantshop.com", "phone": "+2348012345678", "address": "1 Marina", "city": "Lagos", "state": "Lagos", "postal_code": "101001", "country": "NG"},
brand={"name": "Merchant Shop", "postalAddress": "1 Marina, Lagos, Nigeria"},
replies={"mode": "forward", "forwardTo": "ada@gmail.com"},
idempotency_key="order-m_123-1",
)
status = stackshift.partner.get_domain(domain["id"])
partner also exposes overview, update_settings, list_domains (with q, attention and website to find the merchants that need you), connect_domain and transfer_in_domain / transfer_in_connected_domain / switch_transfer_nameservers for domains the merchant already owns, set_website, remove_website, check_website and list_website_projects for the storefront, list_mailboxes, create_mailbox, update_mailbox, create_mailbox_password_link, pause_mailbox, resume_mailbox, remove_mailbox, create_alias, update_alias and remove_alias for merchant mailboxes, start_mail_migration, get_mail_migration, switch_mail_migration, cancel_mail_migration, retry_mail_migration, retry_mail_migration_account and skip_mail_migration_account for moving old email in, retry_domain, get_diagnosis, restore_dns, transfer_out, resume_brand, get_complaints, list_complaint_sources, suppress_never_opened, undo_suppression, usage, list_requests and create_request. transfer_out needs the partner:transfer scope (or *); partner:manage alone is refused. Purchases are capped at 30 per minute and, per partner, at 25 domains in a rolling 24 hours (429 daily_purchase_limit; staff can raise it). Validation errors are 400, state conflicts are 409. The brand and replies dictionaries use API camelCase keys.
Connected agents
An agent that holds a connection credential (issued by the owner, not an account API key) can use the same API the MCP tools and the CLI use:
stackshift = StackShift(api_key=os.environ["STACKSHIFT_CONNECTION_CREDENTIAL"])
before = stackshift.agents.connections.identity().get("before_each_change")
operation = stackshift.agents.connections.operations.propose(
action="deployment.restart", resource_id=project_id, configuration={},
reason="Restart after the config fix", idempotency_key="restart-shop-1",
)
waited = stackshift.agents.connections.operations.wait(operation["id"])
while not waited["settled"]:
waited = stackshift.agents.connections.operations.wait(operation["id"])
agents.connections has identity, resources, capabilities, plan, task_facts(objective_id), history and lower_level(level=..., expected_version=...); agents.connections.operations has list, propose, get, wait and approval. propose needs an idempotency_key: keep it and send the same one to retry. wait returns settled: False when the owner has not decided yet; call it again instead of proposing again. approval(id) says whether the owner has decided, with review_url for the owner. lower_level only makes the connection stricter; raising a level raises StackShiftAPIError with code session_required.
Manage the agents you run on StackShift
hosted_agents manages agents that run on StackShift from your own code or a script. It is separate from agents, which is for agents that hold a connection credential.
stackshift = StackShift(api_key=os.environ["STACKSHIFT_API_KEY"])
agent = stackshift.hosted_agents.create(
{"name": "Supplies agent", "source": {"template": "inbox-helper", "language": "python"}, "level": "ask_every_time"},
idempotency_key="supplies-agent-1",
)
stackshift.hosted_agents.secrets.set(agent["id"], "SHOP_API_KEY", os.environ["SHOP_API_KEY"])
stackshift.hosted_agents.budget.set(agent["id"], {"ai_credits_per_month": 200000, "compute_per_day": {"NGN": "5000000"}})
stackshift.hosted_agents.deploy(agent["id"], branch="main")
runs = stackshift.hosted_agents.runs(agent["id"], limit=10)
hosted_agents has list, get, create, deploy, rollback(id, version), stop(id, reason=None), start, delete, runs, run, spend(id, days=None) and logs(id, offset=None), plus secrets.set, secrets.unset and budget.set. create needs an idempotency_key: keep it and send the same one to retry. Money amounts are in minor units (kobo or cents). To write the agent itself, use stackshift-agent.
Databases: Branches and Sleep when idle
Branches give you a copy of a database for a test run, a preview or a pull request. with_branch makes one, waits until it is ready and always deletes it when the block ends:
with stackshift.databases.branches.with_branch(database_id) as branch:
run_migrations_and_tests(branch["url"])
If the block raises, that error is the one you get back (a failed cleanup is attached as cleanup_error); a slow create is waited out before the Branch is deleted (cleanup_timeout, default 10 minutes). branch["url"] is the shared (pooled) connection string; pass direct=True for the direct one. databases.branches also has list, create, get, update, delete, reset, retry (a failed Branch), promote, operation, wait_for_operation, url, options (what a new Branch can start from), changes and resume_scheduled_jobs. list also returns your plan allowance. create, reset, retry, promote and delete return an operation; wait_for_operation polls it until it succeeds or fails. reset, retry, promote and delete send an Idempotency-Key (a new one for each call if you do not pass idempotency_key). Deleting with cascade=True also needs confirm_name (the Branch's name), or a ValueError is raised before the call is sent. promote accepts the parent's name or, when the parent is a Branch, its short name. databases.sleep, databases.wake, databases.billing, databases.branch_usage, databases.pay_branch_usage, databases.scheduled_jobs, databases.wake_events, databases.move_to_instant_branches and databases.instant_branches_move cover Sleep when idle and the move to instant Branches. Refusals raise StackShiftAPIError with code set to values such as plan_limit, no_capacity, outside_window, too_large and payment_required. On a Dedicated database, create and retry first raise price_confirmation_required with the price in error.details["quote"]; send the same call again with confirm_charge_minor=error.details["quote"]["charge_minor"]. create also takes instance_class to pick the size of a Dedicated Branch, and a Dedicated Branch carries its cost. A plan_limit error carries limit, current and upgrade_url in details.
AI credits
AI credits are account-scoped. Use the shared client to inspect usage, start a top-up, or select managed billing versus your own provider key:
from stackshift import StackShift
stackshift = StackShift()
balance = stackshift.ai_credits.balance()
usage = stackshift.ai_credits.usage(product="assets", limit=25)
quote = stackshift.ai_credits.quote(1_000, "USD")
checkout = stackshift.ai_credits.checkout(
quote["id"],
"unique-checkout-id",
"paystack",
)
stackshift.ai_credits.set_funding("assets", "managed")
Keep account API keys and checkout creation on your server.
Send email
from stackshift import StackShift
stackshift = StackShift()
message = stackshift.mail.send(
from_="StackShift <noreply@mail.stackshift.cloud>",
to="ada@example.com",
subject="Welcome",
text="Welcome to StackShift.",
idempotency_key="welcome:user_123",
)
print(message["id"], message["status"], message["idempotencyStatus"])
Inspect message status:
messages = stackshift.mail.messages.list(status="mta_accepted", limit=20)
detail = stackshift.mail.messages.get(messages["data"][0]["id"])
attempts = stackshift.mail.messages.attempts(detail["id"])
logs = stackshift.mail.messages.logs(detail["id"])
mta_accepted means the message was accepted by StackShift's outbound MTA. It does not mean recipient-MX acceptance, inbox placement, opens, clicks, or spam placement. Recipient records later transition independently to delayed, delivered, bounced, failed, suppressed, or complained.
Events and webhooks
events = stackshift.mail.events.list(
type="mail.message.bounced",
message_id="msg_123",
limit=20,
)
event = stackshift.mail.events.get("evt_123")
timeline = stackshift.mail.messages.timeline("msg_123")
webhook = stackshift.mail.webhooks.create(
url="https://example.com/stackshift-mail",
event_types=["mail.message.bounced", "mail.otp.verified"],
)
print(webhook["id"], webhook["secret"]) # Secret is only returned on create/rotate.
deliveries = stackshift.mail.webhooks.deliveries(webhook["id"], status="failed")
retried = stackshift.mail.webhooks.retry_delivery(deliveries["data"][0]["id"])
Verify a webhook signature before processing the payload:
valid = stackshift.mail.webhooks.verify_signature(
raw_body=request.get_data(),
signature_header=request.headers.get("StackShift-Signature"),
timestamp_header=request.headers.get("StackShift-Timestamp"),
secret=os.environ["STACKSHIFT_WEBHOOK_SECRET"],
)
Webhook handlers should be idempotent. StackShift retries non-2xx responses. Delivery events do not include full email bodies or OTP codes by default.
Mailboxes
Host real mailboxes on your own domain. The mailbox owner gets an invitation by email and sets their own password.
domain = stackshift.mail.mailbox_domains.create("acme.com")
print(domain["records"])
stackshift.mail.mailbox_domains.verify(domain["id"])
mailbox = stackshift.mail.mailboxes.create(
domain_id=domain["id"],
local_part="ada",
owner_email="ada@personal.example",
plan="basic",
)
stackshift.mail.aliases.create(mailbox_id=mailbox["id"], local_part="sales", send_as=True)
stackshift.mail.routes.create(
domain_id=domain["id"],
pattern="orders-*",
targets=[{"kind": "app", "webhookUrl": "https://app.example.com/inbound"}],
)
devices = stackshift.mail.mailboxes.list_devices(mailbox["id"])
stackshift.mail.mailboxes.suspend(mailbox["id"])
stackshift.mail.groups manages team addresses, and stackshift.mail.mailbox_imports copies mail from Google, Microsoft or any IMAP server.
Give your agent an email address in one call
Agent inboxes are real addresses on StackShift's shared domain. Create one, let your agent sign up somewhere, wait for the verification code and delete the inbox when the job is done.
inbox = stackshift.mail.agent_inboxes.create(
prefix="signup",
labels={"job": "onboarding-42"},
job_id="onboarding-42",
expires_in=3600,
idempotency_key="onboarding-42-inbox",
)
sign_up_somewhere(inbox["address"])
message = stackshift.mail.agent_inboxes.wait_for_message(
inbox["id"],
sender="paystack.com",
timeout=120,
since=inbox["createdAt"],
)
if message:
print(message["codes"][0], message["links"])
stackshift.mail.agent_inboxes.finish_job("onboarding-42")
wait_for_message calls the server wait endpoint in turns of up to 50 seconds until a message arrives or timeout (seconds, default 30) is used up, and returns None on timeout. Pass since (usually inbox["createdAt"]) so a message that arrived before you started waiting is found. delete_many takes ids, job_id or labels; finish_job(job_id) is the same as delete_many(job_id=...). create_batch makes up to 100 inboxes at once.
Read mail with agent_inboxes.messages.list, messages.latest(sender=..., subject=...) and messages.get. Find inboxes with agent_inboxes.list(labels=..., job_id=..., q=..., domain=..., limit=..., cursor=...), change them with update, remove one with delete, and check what your account may do with limits() and pricing().
Sending never hides an approval. send returns status as sent, waiting_approval or draft, plus pendingReplyId and reasons when a person has to approve first:
result = stackshift.mail.agent_inboxes.send(
inbox["id"],
to=["ada@example.com"],
subject="Re: your question",
text="Thanks, here is the answer.",
reply_to_message_id=message["id"],
)
if result["status"] == "waiting_approval":
print("Waiting for a person to approve", result["pendingReplyId"], result["reasons"])
Team inboxes
Team inboxes are shared mailboxes your people and agents work together. Use the mailbox id:
page = stackshift.mail.team_inboxes.conversations.list(mailbox_id, status="open", assignee="nobody")
thread = page["conversations"][0]
stackshift.mail.team_inboxes.conversations.assign(mailbox_id, thread["threadId"], worker_id=worker_id, expected_revision=thread["revision"])
stackshift.mail.team_inboxes.conversations.add_note(mailbox_id, thread["threadId"], body="Checked the logs, restarting the app.")
waiting = stackshift.mail.team_inboxes.pending_replies.list(mailbox_id)["pendingReplies"]
stackshift.mail.team_inboxes.pending_replies.send(mailbox_id, waiting[0]["id"])
activity = stackshift.mail.team_inboxes.activity.wait(mailbox_id, after=cursor, timeout=30)
cursor = activity["next"]
conversations.reply(mailbox_id, thread_id, draft_email_id=...) sends a draft you prepared as the inbox, conversations.set_status takes status and expected_revision, settings.get(mailbox_id) reads the inbox settings, and people(mailbox_id) returns {"people": [...], "workers": [...]}: the people (userId, name, email) and workers you can assign conversations to. activity.wait checks for new activity after your cursor every 2 seconds (interval) until something happens or timeout seconds pass. Refusals raise StackShiftAPIError with code set to values such as reply_locked, revision_conflict, reply_already_decided, send_in_progress (409, a send for the same conversation is already running; wait for it to finish) and mail_allowance_reached.
To get inbox events at your own endpoint, create a webhook with mailbox_id and the mailbox.conversation.* events you want, and check it with stackshift.mail.webhooks.test(webhook_id), which returns {"deliveryId": ..., "eventId": ...} once the test event is queued (202).
Inbox webhooks
Inbox events (mailbox.conversation.*, mailbox.agent_inbox.*, mailbox.sending.*) use the same StackShift-Signature header as Mail webhooks:
from stackshift import verify_inbox_webhook
raw_body = request.get_data()
if not verify_inbox_webhook(raw_body, request.headers.get("StackShift-Signature"), os.environ["STACKSHIFT_WEBHOOK_SECRET"]):
return "Invalid signature", 400
event = json.loads(raw_body)
if event["type"] == "mailbox.conversation.received":
print(event["data"]["conversation"]["subject"])
Signatures older than five minutes are refused. Handlers should be idempotent. InboxWebhookEvent and the INBOX_*_EVENT_TYPES lists describe the payloads.
Agent agreements
Agent inboxes can ask for quotes, quote, offer times, and accept or decline offers. Every agreement ends in a record you can check from anywhere.
threads = stackshift.mail.agreements.list(inbox_id=inbox["id"], state="waiting_for_you")
sent = stackshift.mail.agreements.propose(
inbox["id"],
type="ask_quote",
to="orders@mills.example",
items=[{"description": "Flour", "quantity": "200", "unit": "kg"}],
wanted_by="2026-11-10",
idempotency_key="flour-nov-1",
)
change = stackshift.mail.agreements.wait_for_change(sent["threadId"])
stackshift.mail.agreements.accept(sent["threadId"], "OFFER_ID", idempotency_key="flour-accept-1")
record = stackshift.mail.agreements.record(sent["threadId"])
print(stackshift.mail.agreements.check(record)["summary"])
print(stackshift.mail.agreements.limits.get(inbox["id"])["usedToday"])
Whether an agent may send on its own depends on the limits the owner sets for the inbox. Anything outside them waits for the owner's approval. Limits are changed in the dashboard.
Send a template
Templates are rendered by StackShift servers. The Python SDK only calls the REST API.
stackshift.mail.templates.create(
name="Welcome Email",
slug="welcome-email",
subject="Welcome, {{name}}",
text="Welcome, {{name}}",
)
preview = stackshift.mail.templates.preview(
"welcome-email",
data={"name": "Ada"},
)
message = stackshift.mail.send_template(
template="welcome-email",
to="ada@example.com",
from_="Acme <noreply@acme.com>",
data={"name": "Ada"},
idempotency_key="welcome:user_123",
)
Missing variables fail before a message is queued. Template sends use the same sender-domain, suppression, idempotency, Durable Jobs, and Postfix handoff pipeline as mail.send.
Bounces and suppressions
suppressions = stackshift.mail.suppressions.list()
manual = stackshift.mail.suppressions.create(
email="bad@example.com",
reason="manual",
)
stackshift.mail.suppressions.delete(manual["id"])
bounces = stackshift.mail.bounces.list(type="hard")
message_bounces = stackshift.mail.messages.bounces("msg_123")
Hard bounces are automatically suppressed by the backend. Suppressions are workspace scoped and checked before a message is queued.
Verify a sending domain
domain = stackshift.mail.domains.create("acme.com")
print(domain["records"])
stackshift.mail.domains.verify(domain["id"])
message = stackshift.mail.send(
from_="Acme <noreply@acme.com>",
to="user@example.com",
subject="Welcome",
html="<h1>Welcome</h1>",
text="Welcome",
)
Add the returned SPF, DKIM, and return-path DNS records before verification. DMARC is recommended unless your environment sets MAIL_DMARC_REQUIRED=true. DNS propagation can take time.
Upload an asset
from stackshift import StackShift
stackshift = StackShift()
asset = stackshift.assets.upload(
"avatar.png",
folder="avatars",
visibility="public",
metadata={"user_id": "user_123"},
)
print(asset["url"])
You do not pass a deployed StackShift project ID. The API key identifies the StackShift account, and StackShift resolves the default asset space internally.
Private asset URL
signed = stackshift.assets.signed_url(
asset["id"],
expires_in="10m",
max_downloads=1,
)
print(signed["url"])
Image transformations
Use built-in presets or create your own named transformations.
from stackshift import StackShift, asset_transform_options, get_asset_transform_preset
stackshift = StackShift()
hero = get_asset_transform_preset("hero")
hero_options = asset_transform_options(hero)
stackshift.assets.create_transformation(hero["name"], **hero_options)
named = stackshift.assets.named_url("asset_123", hero["name"])
signed = stackshift.assets.signed_transform_url("asset_123", **hero_options, expiresIn="10m")
stackshift.assets.delete_transformation("old-preset")
print(named, signed["url"])
Direct browser uploads
Create the upload session on your Python backend:
upload = stackshift.assets.signed_upload_url(
bucket="avatars",
key="users/user_123.png",
visibility="public",
expiresIn="10m",
maxBytes=5_000_000,
)
Then upload the browser File to upload["url"] from your frontend. Do not expose your StackShift API key to browser code.
The SDK only talks to the StackShift REST API. Storage placement, replication, disks, and repair are StackShift internals.
Campaign drafts and engagement
mail.campaigns.create(content={...}) accepts a private campaign content snapshot; template/version inputs still work. Save the complete draft with update(id, revision=revision, **draft) (draft keys use API camelCase). preview, test, send(id, revision=revision), and schedule(id, send_at, revision=revision) protect the reviewed content. Stale revisions return 409.
Send methods accept tracking={"opens": False, "clicks": True}. Omitted fields inherit workspace defaults. mail.tracking exposes settings, update_settings, message, campaign, and branded-domain methods. Deployment capability flags control availability. Opens are approximate; detailed activity expires after 90 days. OTPs and tests never track.
Isolated testing, brands, migration, and spending
Use a sspat_test_ credential and simulation={"scenario": "delayed"} for a 60-second simulated delay. Delivered, bounced, and complained outcomes are also supported. Test resources are isolated; no real email is delivered.
brand = client.mail.brands.create(name="Product", editable_fields=["company"])
audience = client.mail.audiences.create(brand_id=brand["id"], name="Product news")
Audience creation requires an explicit brand_id. Shared profile fields belong to the brand; consent stays per audience. mail.automations, mail.forms, and mail.segments expose create/list methods; their input dictionaries use API camelCase keys.
mail.migration supports public DNS audits/plans and authenticated checklists. mail.billing.summary() and mail.campaigns.estimate(id) expose allowance and credit. Pass approved_max_kobo=0 (or another approved maximum) alongside revision when sending/scheduling campaigns. Billing remains inactive until a rate card is explicitly published and activated; funding and spending opt-in are separate owner-only dashboard actions.
Native video, governed DAM and product rendering
Use the space-scoped DAM and video clients for collaborators, metadata schemas, bucket governance, reviewed publications, picker capabilities, model preparation, product rendering, galleries, version-pinned encoding, captions and playback sessions.
dam = sdk.assets.dam.for_space(space_id)
video = sdk.assets.video.for_space(space_id)
schema = dam.create_metadata_schema({"name": "Products", "fields": [{
"key": "product_name", "label": "Product name", "type": "text", "required": True,
}]})
published = dam.publish_metadata_schema(schema["id"], schema["revision"])
workspace = video.workspace(asset_id)
Mutations carry the supplied revision in If-Match. Model preparation and rendering carry an idempotency key; render submission also carries the accepted maximum units. Playback renewal and events authenticate with the session credential, not the account key. Keep these clients on your backend and return grants with Cache-Control: private, no-store.
See the Assets SDK guide and its linked feature guides for complete language examples, inputs, permissions, review transitions and browser callbacks.
Assets storage and Cloudinary migration
sdk.assets.platform.for_space(space_id) exposes connections, storage, and migrations. Use a verified Cloudinary connection, run a dry migration first, inspect its report, then start the transfer with the same folder prefix and conflict policy.
platform = sdk.assets.platform.for_space(space_id)
preview = platform.migrations.start({"source_connection_id": connection_id,
"destination_bucket_id": bucket_id, "source_prefix": "campaigns/2026", "dry_run": True})
report = platform.migrations.report(preview["id"])
Support action receivers (unreleased)
ActionManifest, ActionDescriptor, installation consent types and validate_action_catalog describe stackshift.support.app/v2. V1 remains actionless. Registered writes require idempotency, a reconciliation endpoint and a registered read verifier; installations explicitly select action_operations. These SDK helpers perform no registration or network calls.
Use receive_action(raw_bytes, headers, store=..., execute=..., reconcile=..., verification=...). Verification requires key_id, secret (the installation action secret, not webhook secret), installation_id, actions, and a trusted expected_customer_external_id. Do not derive that expectation from unverified request JSON. Each synchronous handler returns (state, result_dict, optional_safe_error_code). Success output is checked against the registered flat schema. Receipt helpers encode_receipt, sign_action, and verify_receipt are also exported.
SQLiteActionStore('/absolute/path/claims.sqlite') uses standard-library sqlite3, WAL and FULL synchronization. Never use an in-memory database for restart safety. The runnable stdin receiver in examples/support_action_receiver.py intentionally returns demo_not_implemented; replace its handlers with authorized customer-bound business transactions. Set ACTION_KEY_ID, ACTION_SECRET, ACTION_INSTALLATION_ID, and trusted EXPECTED_CUSTOMER_EXTERNAL_ID:
PYTHONPATH=. python examples/support_action_receiver.py /absolute/path/claims.sqlite headers.json actions.json < body.json
.venv/bin/python -m unittest discover -s tests -p test_support_actions.py -v
Send the returned body unchanged with headers. Execution retries retain exact original bytes and request ID with fresh delivery IDs. A handler error is stored as unknown; running/unknown work is never dispatched again. Reconciliation is a separate handler with empty arguments and the original target request ID, and must query actual downstream state. Accepted results remain replayable; there is no asynchronous callback endpoint yet. Disk claims do not make remote side effects atomic: use downstream idempotency or one transactional business/receipt store, and preserve uncertain claims for reconciliation/human review. This is a single-host SQLite example; distributed receivers need a shared durable store.
See ../support-actions-conformance/README.md for exact signatures and adapter requirements. Seven action tests pass, including shared vectors, two-connection concurrency, restart and fenced completion. The existing virtualenv is required here because system Python lacks the SDK's declared requests dependency. No live installation/provider qualification was performed; unreleased.
Metadata
Release files for stackshift 1.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| stackshift-1.7.0.tar.gz | 81.6 kB | Without provenance | Details for stackshift-1.7.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| stackshift-1.7.0-py3-none-any.whl | Python 3 | none | any | Details for stackshift-1.7.0-py3-none-any.whl |
Total release size: 139.4 kB
Release files / stackshift-1.7.0.tar.gz
| Download URL | stackshift-1.7.0.tar.gz |
|---|---|
| Size | 81.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0381bb041052dd80b54f6bca5923711cb84dd89feb26127622584a3413943fb0
|
|
BLAKE2b-256 checksum How to use checksums |
c93b7aa0419b40f6af28121e8e39cec5b78ea337e812c25080be23bd9647d937
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.12
|
Release files / stackshift-1.7.0-py3-none-any.whl
| Download URL | stackshift-1.7.0-py3-none-any.whl |
|---|---|
| Size | 57.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a7ee32d6b0ff44facde73e89f1ab018b26534a7aabf0ff51c9e1782424fef659
|
|
BLAKE2b-256 checksum How to use checksums |
3a499205fc82fe69f0f3299a53e7f8cc714fc14d112f28f18db8f817e2ed385c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.12
|
Security for stackshift 1.7.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.