Skip to main content

StackShift Python SDK

Official Python SDK for StackShift.

Install

pip install stackshift

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.

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.3.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 stackshift 1.3.0
File Size Uploaded Provenance Actions
stackshift-1.3.0.tar.gz 47.6 kB Without provenance Details for stackshift-1.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for stackshift 1.3.0
File Interpreter ABI Platform Actions
stackshift-1.3.0-py3-none-any.whl Python 3 none any Details for stackshift-1.3.0-py3-none-any.whl

Total release size: 87.7 kB

Release files / stackshift-1.3.0.tar.gz

Download URL stackshift-1.3.0.tar.gz
Size 47.6 kB
Tags Source
SHA-256 checksum
How to use checksums
821d21b67738075d7c4e5aae500245a3a19cfb002d8b704bbc3108f2869c4d08
BLAKE2b-256 checksum
How to use checksums
a83290ac02d08b0d2e41becea53e6709e81bb589cad110026435742dd0dd6782
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.18

Release files / stackshift-1.3.0-py3-none-any.whl

Download URL stackshift-1.3.0-py3-none-any.whl
Size 40.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d334d4fc87455362f0d6a9b9b7139606988a6023abb4bbf1cc6be7909fedb0a7
BLAKE2b-256 checksum
How to use checksums
53d7e4cdb36200f9f6d754c64a547e3321e4a4ae81c7ff1e0f1d9a555413d864
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.18

Security for stackshift 1.3.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.

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