Official Python SDK for ReplyLayer — email for AI agents
Project description
replylayer
Official Python SDK for ReplyLayer — secure email for AI agents.
Install
pip install replylayer
Quick start
from replylayer import ReplyLayer
rl = ReplyLayer(api_key="rly_live_k3m9p2qx7vn4hjd0.uZ8Qb1vK3mN0pR7sT2wX9yA4cF6gH8jL1nP3rT5vW7z")
# Create a mailbox
mailbox = rl.mailboxes.create(name="support")
# Send an email
sent = rl.messages.send(
from_mailbox=mailbox["name"],
to="user@example.com",
subject="Hello from my agent",
body="Hi there!",
)
# Wait for a reply (long-poll, up to 30s)
result = rl.messages.wait(mailbox["id"])
if result["message"]:
msg = result["message"]
print(f"Got reply from {msg['sender']}: {msg['subject']}")
# Browse conversation threads
page = rl.threads.list(mailbox["id"])
for thread in page.data:
print(f"{thread['subject']} ({thread['message_count']} messages)")
Async usage
from replylayer import AsyncReplyLayer
async with AsyncReplyLayer(api_key="rly_live_k3m9p2qx7vn4hjd0.uZ8Qb1vK3mN0pR7sT2wX9yA4cF6gH8jL1nP3rT5vW7z") as rl:
mailbox = await rl.mailboxes.create(name="support")
sent = await rl.messages.send(
from_mailbox=mailbox["name"],
to="user@example.com",
subject="Hello",
body="Hi!",
)
Constructor options
ReplyLayer(
api_key="rly_live_k3m9p2qx7vn4hjd0.uZ8Qb1vK3mN0pR7sT2wX9yA4cF6gH8jL1nP3rT5vW7z", # required
base_url="https://api.replylayer.ai", # default
max_retries=3, # retries on 429/5xx (0 = fail-fast)
timeout=30.0, # seconds per request
max_retry_after_seconds=4000.0, # cap on honoring a 429 Retry-After (~67min)
on_retry=None, # silent-by-default retry hook
)
Retry behavior
The client retries failed requests up to max_retries times (default 3). The
contract — read it before relying on retries:
429is retried on every method, including mutating ones (POST/PATCH/DELETE). A429is a pre-dispatch rate-limit rejection, so nothing happened server-side — retrying is safe. The wait honors theRetry-Afterheader.5xxis retried only on non-mutating (GET) requests. A5xxon aPOST/PATCH/DELETEis not retried — the request may have executed, so a retry risks a double-send (or, forDELETE, retrying a lost-but-applied delete into a confusing404).- Multipart uploads are never retried (a retry would re-send the body).
- Long
Retry-Aftervalues block up tomax_retry_after_seconds(default ~67 minutes, sized to ride out hour-bucket rate limits for batch jobs). When a serverRetry-Afterexceeds this cap, the SDK raises theRateLimitErrorrather than sleeping — it never clamps-and-retries into a still-limited window. Interactive callers should set a low cap (e.g.max_retry_after_seconds=30). max_retries=0is fail-fast — no implicit retry of any kind. Recommended for interactive / agent contexts. Branch onRateLimitError.retry_after.on_retryis silent by default — the SDK never writes to stdout/stderr. Pass anon_retry(info)hook to log or meter retries; it receives aRetryInfo(attempt,error,delay_seconds,method,path). On the async client it may be a coroutine (it's awaited); a raising callback is swallowed so it can't break the retry.
Resources
| Resource | Methods |
|---|---|
rl.mailboxes |
create, list, delete, update, set_recipient_policy |
rl.mailboxes.allowlist |
list, add, add_bulk, delete, list_blocked_attempts |
rl.messages |
send, list, get, reply, wait, release, block |
rl.drafts |
create, get, list, update, send, delete |
rl.threads |
list, get |
rl.attachments |
get_download_url, get_preview, upload, get_upload, delete_upload |
rl.webhooks |
create, list, get, update, delete, rotate_secret, test, list_deliveries, retry_delivery |
rl.recipients |
create, list, delete, resend |
rl.suppressions |
list, delete |
rl.api_keys |
create, list, revoke, rotate* |
rl.account |
get_usage |
rl.health |
check |
*api_keys.rotate() revokes the calling API key and returns a new one. After calling it, this SDK instance's key is invalidated — create a new ReplyLayer instance with the returned key.
Drafts: scan-then-review-then-send
rl.drafts.create() runs the outbound scanner synchronously and attaches the verdict to the draft. The create-time verdict is UX — it lets an agent (or a human approver) see the likely outcome before clicking send. rl.drafts.send() re-runs the scanner authoritatively against the mailbox's current policy, so a stale cached verdict cannot slip through.
draft = rl.drafts.create(
mailbox_id=mailbox["name"],
to="user@example.com",
subject="Re: your invoice",
body="Thanks for your question.",
)
if draft["worst_decision"] == "allow":
result = rl.drafts.send(draft["id"])
print(f"Sent {result['message_id']}")
The send/reply/draft-send response carries two additive, nullable keys that explain a held send inline (no second messages.get call). result["scan"] is the vendor-neutral scanner verdict (ScanSummary); result["hold_context"] ({"trigger_source", "summary_reasons"} or None) is the policy/HITL reason, non-null only when the delivery status diverges from scan["verdict"] because of a policy/HITL hold — a clean scan held for review by your mailbox policy, or a scanner review-flag held as quarantine on a plan without the review queue (trigger_source: mailbox_policy | scanner | both).
This SDK always sends synchronously — drafts.send(), messages.send(), and messages.reply() return only once the scanner verdict is known, with scan and hold_context inline. The optimistic-ack async path (Prefer: respond-async → 202 queued_for_dispatch, then poll the message to a terminal state) is a REST-level capability of POST /v1/drafts/:id/send only; the SDK exposes no Prefer option. To use it, drive that route directly (see ENDPOINTS.md "Asynchronous send (optimistic-ack) & polling") and poll messages.get(message_id) (or handle the lifecycle webhook) until state is terminal. (messages.wait() is a mailbox long-poll for new inbound mail, not a way to observe a specific message by ID.)
The send endpoint raises ReplyLayerError with distinct .code values on 409:
DRAFT_REJECTED_BY_RESCAN— send-time scan flipped the verdict toblock/quarantine. The draft stays indraftstate; edit the body and retry.err.detailscarriesscanand, when a policy/HITL decision drove the hold,hold_context.DRAFT_ALREADY_SENT— the draft was already sent (race or retry after success).
from replylayer import ReplyLayerError
try:
rl.drafts.send(draft["id"])
except ReplyLayerError as err:
if err.code == "DRAFT_REJECTED_BY_RESCAN":
print("Rescan blocked it:", err.details)
Outbound attachments (Pro+)
Attaching a file is a two-phase flow: upload the bytes to stage a handle, then reference handle["id"] in a send/reply/draft attachment_ids list. Every attachment is scanned (byte-level family validation + AV + secrets/PII over extracted text and filename) before it leaves. The mailbox must have outbound attachments explicitly enabled (a Pro+, session-gated dashboard action) — uploads to a non-enabled mailbox raise ForbiddenError with code="OUTBOUND_ATTACHMENTS_DISABLED".
import time
# Phase 1 — stage the file (returns an opaque handle).
with open("invoice.pdf", "rb") as f:
handle = rl.attachments.upload(
mailbox_id="support",
file=f.read(), # bytes or a file-like object
filename="invoice.pdf",
content_type="application/pdf", # advisory — the server re-sniffs the bytes
)
# The content scan runs asynchronously. Poll until it leaves "pending".
status = handle["content_scan_status"] # "pending" at upload time
while status == "pending":
time.sleep(1)
polled = rl.attachments.get_upload(handle["id"])
if polled.get("status") == "consumed":
break
status = polled["content_scan_status"]
# Phase 2 — reference the handle on a send. "clean" and "flagged" both send
# (a "flagged" finding flows to the message verdict, like a body finding);
# "error" is fail-closed.
result = rl.messages.send(
from_mailbox="support",
to="user@example.com",
subject="Your invoice",
body="Attached.",
attachment_ids=[handle["id"]],
)
A handle is consumed once at send and is single-mailbox-scoped (upload to the same mailbox you send from). Unconsumed handles expire after 24h; delete one early with rl.attachments.delete_upload(handle["id"]). Limits: 10 MB/file, 10 attachments and 15 MB total per message. Image attachments require a separate one-time image-risk disclaimer on the mailbox (OUTBOUND_IMAGE_DISCLAIMER_REQUIRED). Drafts hold handles and consume them at dispatch; rl.drafts.update(draft_id, attachment_ids=None) clears a draft's attachments. Attachment bytes are stored with provider-managed encryption-at-rest and transmitted over TLS — this is not end-to-end / zero-access encryption (the platform scans attachment content).
Delivery history & manual retry
rl.webhooks.list_deliveries(id, limit=..., before_at=..., before_id=...) returns the most recent delivery attempts for a webhook with tuple-cursor keyset pagination. before_at and before_id must be provided together — the SDK omits the cursor entirely if only one is given.
page = rl.webhooks.list_deliveries(webhook_id, limit=50)
while page["has_more"]:
page = rl.webhooks.list_deliveries(
webhook_id,
limit=50,
before_at=page["next_before_at"],
before_id=page["next_before_id"],
)
rl.webhooks.retry_delivery(webhook_id, delivery_id) re-queues a single failed delivery. The API rejects retries on non-failed deliveries or deliveries whose parent webhook is disabled — surfaced as ReplyLayerError with .code set to DELIVERY_NOT_FAILED or WEBHOOK_DISABLED:
from replylayer import ReplyLayer, ReplyLayerError
try:
rl.webhooks.retry_delivery(webhook_id, delivery_id)
except ReplyLayerError as err:
if err.code == "WEBHOOK_DISABLED":
# Resume the webhook (PATCH enabled=True) before retrying.
pass
Mailbox settings
Each mailbox carries a scanner policy and a PII delivery mode:
# Redact PII before delivering inbound bodies to the agent
rl.mailboxes.update(
mailbox["id"],
scanner_policy={"language_mode": "english_only"},
pii_mode="redacted",
)
pii_mode values:
"passthrough"(default) — message reads returnbody.contentas a plaintext display projection. Session-cookie dashboard callers can opt into sanitized HTML withbody_format=html."redacted"—body.contentis plaintext with detected PII spans replaced by<TYPE>tags (e.g.<EMAIL_ADDRESS>,<PHONE_NUMBER>). Requires Starter tier or above; sandbox accounts get403 TIER_LIMIT.
pii_mode="redacted" also applies to outbound webhook payloads: message.* events have sender/recipient/to → <EMAIL_ADDRESS> and subject → <REDACTED> before signing. The HMAC covers the redacted body — verify_webhook_signature works without any client-side changes.
Advanced PII config (Pro+)
PR 8 added pii_redaction_config for per-detector control over redaction (e.g. "leave email visible, redact everything else") and operator-level rendering (partial_mask for credit cards, hash_replace for emails you want to dedupe without exposing). Pro+ feature; only meaningful when pii_mode="redacted".
# Per-detector toggle: show emails to the agent, keep everything else redacted.
rl.mailboxes.update(
mailbox["id"],
pii_mode="redacted",
pii_redaction_config={
"EMAIL_ADDRESS": {"redact": False},
},
)
# partial_mask: render credit cards as ****-****-****-1111 (separators preserved).
# `keep_last` is 1-6; `mask_char` defaults to "*".
rl.mailboxes.update(
mailbox["id"],
pii_redaction_config={
"CREDIT_CARD": {
"redact": True,
"operator": {"kind": "partial_mask", "keep_last": 4},
},
},
)
# hash_replace: <EMAIL_ADDRESS:a3f1b9c2>. Deterministic per account; opaque
# across accounts. Lets your agent dedupe without seeing raw values.
rl.mailboxes.update(
mailbox["id"],
pii_redaction_config={
"EMAIL_ADDRESS": {
"redact": True,
"operator": {"kind": "hash_replace"},
},
},
)
# Reset to platform default.
rl.mailboxes.update(mailbox["id"], pii_redaction_config={})
Tier gate. Any non-default value (a redact: False entry OR an operator with kind: "partial_mask" or kind: "hash_replace") requires the pii_advanced_controls feature (Pro+). Non-feature accounts can still PATCH {}, default-shape entries ({"redact": True}, {"kind": "replace_with_type"}).
partial_mask whitelist. PERSON and EMAIL_ADDRESS are rejected (422) — partial-masking a name produces nonsense; partial-masking an email is hard to do well in v1. Use hash_replace for those instead.
Downgrade behavior. If you configure non-default pii_redaction_config on Pro and then downgrade, the persisted JSONB stays on the row but the read-side IGNORES it. Reads fall back to platform default. Re-upgrading restores the config instantly. The dashboard surfaces a "Saved but inactive" banner in this state.
Webhook scope-out. Advanced PII config does NOT apply to webhook payload metadata. Webhook delivery continues to use pii_mode for envelope-level field redaction; per-detector and operator control is API read-side only.
The Python SDK ships static type hints for PiiOperator (a Union of PiiReplaceWithTypeOperator, PiiPartialMaskOperator, and PiiHashReplaceOperator TypedDicts) — so a config like {"kind": "hash_replace", "keep_last": 4} is caught by mypy / pyright at the SDK boundary, not just at the server's 422.
Outbound PII send safety. ScannerPolicy.outbound_pii_policy tunes send decisions for the local outbound PII scanner by type:
rl.mailboxes.update(
mailbox["id"],
scanner_policy={
"outbound_pii_policy": {
"ssn": "quarantine",
"credit_card": "review",
"phone_number": "allow_with_warning",
},
"outbound_review_policy": {
"approval_note": "required_for_sensitive_pii",
},
},
)
Supported actions are "allow", "allow_with_warning", "review", "quarantine", and "block". "review" routes matching sends to Pending approval; enabling it requires both Pro+ outbound PII controls and the review queue feature. Relaxing below platform defaults requires Pro+ (pii_advanced_controls); default or stricter values are accepted on every tier. Outbound PII scan results include pii_type ("ssn", "credit_card", or "phone_number") so clients can inspect which type drove the action.
Approval notes are optional by default. Set outbound_review_policy.approval_note to "required_for_sensitive_pii" when approvers must add a note before sending SSN or credit-card review holds.
Agent Attachment Access
Effective attachment exposure now comes from the mailbox policy surface (attachment_exposure_mode plus attachment_allowed_file_families), not from the legacy attachment_access_enabled boolean alone. Admin keys, pre-scoping keys, and dashboard sessions still bypass the agent mailbox-policy gate. Agent-key download requests without an explicit raw-download policy return 403 ATTACHMENT_ACCESS_DISABLED — surfaced as ReplyLayerError with .code == "ATTACHMENT_ACCESS_DISABLED":
from replylayer import ReplyLayerError
try:
rl.attachments.get_download_url(message_id, 0)
except ReplyLayerError as err:
if err.code == "ATTACHMENT_ACCESS_DISABLED":
# Admin can configure the mailbox attachment policy through the
# dashboard or POST /v1/mailboxes/:id/attachment-access.
...
Explicit raw_download_selected_types enablement requires a Pro+ production account, session-cookie auth, and fresh TOTP/password re-auth, so Bearer-key SDK clients receive 403 REAUTH_REQUIRES_SESSION when they try to enable or widen approved downloads. The SDK can still read attachment policy state, disable raw downloads, set metadata_only / derived_content, and perform same-or-narrower writes on an already-explicit approved-download mailbox.
Images are a separately confirmed raw-download family. When allowed_file_families includes "image", pass accept_image_risk_version matching the mailbox response's current_image_risk_version unless the mailbox already has current image-risk acceptance. A mailbox response reports image state with image_raw_download_confirmed, attachment_image_access_accepted_at, and attachment_image_access_accepted_version. Legacy wildcard rows and stale image acceptances do not grant raw image downloads.
Human dashboard sessions and admin/pre-scoping keys can download clean stored metadata_only attachments, including attachments held back from agent raw-download policy. Agent-role keys remain bound to the mailbox policy gate plus hard safety checks; all callers remain blocked by infected AV verdicts, non-terminal message states, missing stored bytes, and hard attachment blocks.
See ENDPOINTS.md for the full contract and known limitations.
Recipient allowlist (mailbox containment)
A mailbox is in blocklist mode by default — the pre-send gate rejects suppressed_addresses hits and allows everyone else. Switching to allowlist mode restricts outbound to a pre-approved list; an agent (or a compromised API key) physically cannot email outside the list.
# Populate the allowlist first. Admin-only — agent keys get 403 INSUFFICIENT_SCOPE.
rl.mailboxes.allowlist.add(mailbox["id"], email="partner@corp.com")
rl.mailboxes.allowlist.add_bulk(mailbox["id"], emails=["a@x.com", "b@y.com"])
# Flip the mode. Returns 400 ALLOWLIST_EMPTY if the list is empty unless
# force_empty=True is passed to acknowledge the lockout.
rl.mailboxes.set_recipient_policy(mailbox["id"], "allowlist")
# Sends to off-list recipients now 403 with code RECIPIENT_NOT_ON_ALLOWLIST.
# Blocklist still runs first — a recipient on the do-not-contact list is
# rejected 403 with code RECIPIENT_SUPPRESSED (details["reason"] == "suppressed").
# Deleting the last entry while in allowlist mode returns 409 ALLOWLIST_LAST_ENTRY;
# pass force_empty=True to acknowledge.
rl.mailboxes.allowlist.delete(mailbox["id"], "partner@corp.com", force_empty=True)
A send/reply/draft-send to a recipient on your do-not-contact (suppression) list raises ReplyLayerError with .code == "RECIPIENT_SUPPRESSED" (HTTP 403, details["reason"] == "suppressed"). This is terminal — escalate, don't retry; remove the suppression or send to a different recipient.
Allowlist mutations are admin-only — granting send permission to an LLM defeats the containment boundary. Agents can list (so they can see what they're allowed to email) but not add/add_bulk/delete. Three new webhook events: recipient_allowlist.added, recipient_allowlist.removed, mailbox.recipient_policy_changed.
Domain entries (sprint 039)
Entries can be either an exact email (alice@corp.com) or a bare-domain pattern (@corp.com) that matches every address at that domain. Exact-domain only — @corp.com matches *@corp.com but NOT eve@sub.corp.com.
# Allow everyone at @partner.com.
rl.mailboxes.allowlist.add(mailbox["id"], email="@partner.com")
# Block a whole competitor domain.
rl.suppressions.add(email="@competitor.com")
# Bulk mix emails + domains.
bulk = rl.mailboxes.allowlist.add_bulk(
mailbox["id"],
emails=["alice@corp.com", "@partner.com", "not-an-email"],
)
# bulk["added"][0]["pattern_type"] == "email"
# bulk["added"][1]["pattern_type"] == "domain"
# bulk["invalid"][0] == {"email": "not-an-email", "reason": "invalid_format"}
Responses expose pattern_type: "email" | "domain" on every add/list/delete/bulk-added row. Pre-0.5.0 servers omit the field.
Blocklist precedence still holds: a domain-block beats an exact-allow at the same domain. Malformed patterns (@, @.com, @foo, @corp-.com, non-ASCII) raise ReplyLayerError with .code == "INVALID_EMAIL" (message: "Invalid email or domain pattern").
Blocked attempts (migration 038)
Every send the allowlist gate rejects writes an append-only audit row and emits a deduped recipient_allowlist.blocked_attempt webhook. Review the log to see what your agent tried to email and one-click add legitimate recipients.
# Aggregated top-N view — grouped by (recipient, actor_id).
# next_cursor is always None; the aggregate is top-N, not paginated.
result = rl.mailboxes.allowlist.list_blocked_attempts(mailbox["id"])
for a in result["attempts"]:
print(f"{a['recipient']} × {a['count']} (last: {a['last_attempted_at']})")
# "Blocked this week" — recency filter (1..365 days).
week = rl.mailboxes.allowlist.list_blocked_attempts(mailbox["id"], within_days=7)
# Raw per-attempt history for forensic drill-in. Paginates via tuple cursor.
raw = rl.mailboxes.allowlist.list_blocked_attempts(
mailbox["id"], aggregate=False, limit=100,
)
Async parity is identical — await rl.mailboxes.allowlist.list_blocked_attempts(...).
Webhook deliveries are deduped server-side to at most one per (account, mailbox, recipient) per 60 seconds — a looping agent produces one delivery, not hundreds, keeping your subscription below the 20-abandoned-deliveries auto-disable threshold. Full attempt history is always available via list_blocked_attempts.
The MCP tool list_allowlist_blocked_attempts exposes the same view to agents — read-only by design. There is no dismiss-attempt tool (the containment boundary would be moot if an agent could clear its own rejection history).
Mailbox identifiers
Every SDK method that takes a mailbox_id argument accepts either the mailbox's UUID or its name. The server resolves names against the authenticated account's active mailboxes. rl.messages.list("support-bot") and rl.messages.list("a1b2-…") are equivalent.
Pagination
List endpoints return a Page with data, has_more, and cursor:
page = rl.messages.list("mailbox-id", limit=50)
print(page.data) # list of message dicts
print(page.has_more) # bool
print(page.cursor) # str | None
Pass auto_paginate=True for an iterator:
for msg in rl.messages.list("mailbox-id", auto_paginate=True):
print(msg["subject"])
# Async
async for msg in rl.messages.list("mailbox-id", auto_paginate=True):
print(msg["subject"])
Error handling
from replylayer import ReplyLayer
from replylayer.errors import NotFoundError, RateLimitError
try:
rl.messages.get("nonexistent")
except NotFoundError:
print("Message not found")
except RateLimitError as e:
print(f"Rate limited, retry after {e.retry_after}s")
Error classes: ReplyLayerError (base), AuthenticationError (401), ForbiddenError (403), NotFoundError (404), ValidationError (400/422), RateLimitError (429).
Webhook signature verification
For a full integration guide (event catalog, retry behavior, idempotency, security, troubleshooting), see
docs/webhooks.md.
from replylayer import verify_webhook_signature
verify_webhook_signature(
payload=request.body,
signature=request.headers["x-replylayer-signature"],
secret="whsec_...",
tolerance=300, # optional, seconds (default 300)
)
Context managers
Both clients support context managers to properly close connection pools:
with ReplyLayer(api_key="...") as rl:
rl.messages.send(...)
# connections closed
async with AsyncReplyLayer(api_key="...") as rl:
await rl.messages.send(...)
Requirements
- Python >= 3.10
- httpx >= 0.27
License
MIT
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 replylayer-0.14.0.tar.gz.
File metadata
- Download URL: replylayer-0.14.0.tar.gz
- Upload date:
- Size: 77.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
78c75b0669c3a13e09428895f0fe68a61c6190088abfe444f47fcd19613cbbd2
|
|
| MD5 |
39bf734a3acb090cf8b2d280cde47be8
|
|
| BLAKE2b-256 |
a3ddbcef84671d903f683f01b782b4466362f14ef248dc39940416d8a263fb1f
|
Provenance
The following attestation bundles were made for replylayer-0.14.0.tar.gz:
Publisher:
publish-pysdk.yml on replylayer/ReplyLayer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
replylayer-0.14.0.tar.gz -
Subject digest:
78c75b0669c3a13e09428895f0fe68a61c6190088abfe444f47fcd19613cbbd2 - Sigstore transparency entry: 1742553042
- Sigstore integration time:
-
Permalink:
replylayer/ReplyLayer@78218066f1fca51b5b80f127178bfc197c9d4275 -
Branch / Tag:
refs/tags/pysdk-v0.14.0 - Owner: https://github.com/replylayer
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pysdk.yml@78218066f1fca51b5b80f127178bfc197c9d4275 -
Trigger Event:
push
-
Statement type:
File details
Details for the file replylayer-0.14.0-py3-none-any.whl.
File metadata
- Download URL: replylayer-0.14.0-py3-none-any.whl
- Upload date:
- Size: 56.2 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 |
6bfde53e52e1b3cdae5471377d36650396a381b542384fbdd6e201a97962bc59
|
|
| MD5 |
e2d100531d8157fdf6722a9acd068ac9
|
|
| BLAKE2b-256 |
c6634605c3c7d13829c34999dbb98f4ab7911adbefaf5d35543ecd5697af60f8
|
Provenance
The following attestation bundles were made for replylayer-0.14.0-py3-none-any.whl:
Publisher:
publish-pysdk.yml on replylayer/ReplyLayer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
replylayer-0.14.0-py3-none-any.whl -
Subject digest:
6bfde53e52e1b3cdae5471377d36650396a381b542384fbdd6e201a97962bc59 - Sigstore transparency entry: 1742553115
- Sigstore integration time:
-
Permalink:
replylayer/ReplyLayer@78218066f1fca51b5b80f127178bfc197c9d4275 -
Branch / Tag:
refs/tags/pysdk-v0.14.0 - Owner: https://github.com/replylayer
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pysdk.yml@78218066f1fca51b5b80f127178bfc197c9d4275 -
Trigger Event:
push
-
Statement type: