Veypost Python SDK
Python SDK (0.1.1) for the public publishing API. Requires Python 3.11+. JavaScript, analytics and server-side bulk jobs remain outside this release.
Install
Once version 0.1.1 is published to PyPI:
pip install veypost
Install from this checkout
From the repository root, in your own virtual environment:
python -m pip install -e ./packages/python-sdk
export VEYPOST_API_KEY="YOUR_API_KEY"
export VEYPOST_API_URL="http://localhost:8000"
# Optional: select an explicitly allowed workspace.
# export VEYPOST_WORKSPACE_ID="YOUR_WORKSPACE_UUID"
A local backend needs its existing public API feature configuration, migrations, eligible subscription and scoped key; installing this client does not enable those gates.
Connect and discover accounts
import os
from veypost import Veypost
with Veypost(api_key=os.environ["VEYPOST_API_KEY"]) as client:
accounts = client.accounts.list(limit=10)
for account in accounts.data["items"]:
print(account["id"], account["provider"], account["display_name"])
Veypost() reads VEYPOST_API_KEY automatically. Configuration priority is explicit argument, then environment, then built-in default. base_url defaults to https://api.veypost.blanchorizon.com; set VEYPOST_API_URL for local use. The production default is configuration, not evidence of hosted public API availability.
import os
from veypost import Veypost
client = Veypost(
api_key=os.environ["VEYPOST_API_KEY"],
workspace_id=os.environ.get("VEYPOST_WORKSPACE_ID"),
base_url="http://localhost:8000",
timeout=30.0,
max_retries=2,
upload_timeout=300.0,
)
try:
page = client.posts.list(status="draft")
finally:
client.close()
This is a synchronous, server-side client. Use a context manager or close(). Use a separate client per concurrent worker; upload helpers transfer sequentially. Environment proxies are disabled and redirects are not followed. HTTPS is required except for loopback HTTP. base_url is the API origin, without /api/public/v1.
Publishing workflow
Each Python snippet below opens and closes its own client. Set VEYPOST_ACCOUNT_ID
to a discovered account UUID for draft creation. Existing-post examples use
VEYPOST_POST_ID; publication commands use a persisted UUID in VEYPOST_COMMAND_ID.
- Select an allowed workspace and discover its accounts; create a draft.
- Upload files if needed; wait for verification and attachment.
- Validate the draft and inspect issues.
- Retrieve the latest ETag.
- Explicitly request publication or scheduling.
- Follow the returned publication request and individual delivery states.
Every result is APIResponse[T]: typed dictionary .data, .status_code, .etag, .request_id, .idempotency_key. The response representation hides data because upload responses can contain temporary secrets. Generated TypedDicts are available as veypost.models; runtime request validation uses the same bundled OpenAPI contract.
Create and edit drafts
import os
from uuid import uuid4
from veypost import Veypost
with Veypost(
api_key=os.environ["VEYPOST_API_KEY"],
base_url=os.environ["VEYPOST_API_URL"],
workspace_id=os.environ.get("VEYPOST_WORKSPACE_ID"),
) as client:
# Save this UUID before sending; reuse it only for an exact retry.
command_id = str(uuid4())
draft = client.posts.create(
base_caption="My first API draft",
destinations=[{"social_account_id": os.environ["VEYPOST_ACCOUNT_ID"]}],
idempotency_key=command_id,
)
print(draft.data["id"], draft.etag)
edited = client.posts.update(
draft.data["id"],
changes={"base_caption": "My reviewed caption"},
if_match=draft.etag,
idempotency_key=str(uuid4()),
)
print(edited.data["id"], edited.etag)
Set VEYPOST_ACCOUNT_ID to an ID from client.accounts.list(). All content belongs directly to the selected workspace. Set workspace_id=... per call to override the client’s default; the API key must allow that workspace.
Edits send only changes. Omit unchanged fields; only title may explicitly be None. The client requires the exact quoted ETag. On 412, retrieve and review the post; it never silently refreshes a version and overwrites another edit.
Upload local files
import os
from pathlib import Path
from veypost import Veypost
with Veypost(
api_key=os.environ["VEYPOST_API_KEY"],
base_url=os.environ["VEYPOST_API_URL"],
workspace_id=os.environ.get("VEYPOST_WORKSPACE_ID"),
) as client:
post_id = os.environ["VEYPOST_POST_ID"]
current = client.posts.retrieve(post_id)
plan = client.uploads.prepare(
post_id, [Path("photo.jpg")], if_match=current.etag,
)
# Save plan.to_dict() privately before sending. Retain IDs and checksums.
client.uploads.send(plan)
client.uploads.wait_until_ready(plan, timeout=180)
current = client.posts.retrieve(post_id)
print(current.data["id"], current.etag)
prepare hashes files in 1 MiB blocks, validates supported MIME types/sizes and generates stable reservation IDs without a network call. send rechecks file content, reserves a bounded batch, and streams bytes. Signed resumable uploads use 6 MiB chunks and reconcile ambiguous failures with HEAD. Signed PUT is supported when no resumable ticket is returned. Only the API-issued storage headers are used; the Veypost key is never attached to storage requests. Resumable session URLs stay in memory; a restored plan reserves the same IDs and can obtain fresh capabilities, but does not promise cross-process byte-offset recovery. Retrying after a response loss may require checking current attachment state first.
A batch permits at most 20 reservation items, while aggregate post/file/provider limits still apply. This is multiple-file upload for one post, not a bulk-post API. Existing attachment positions start at zero; pass start_position when appending. For a cover, use UploadFile(path, purpose="cover", video_id=..., frame_ms=...).
send completion means bytes transferred, not verified or published. wait_until_ready watches only the plan's attachment IDs and stops on attention/removed; a timeout does not cancel work. Inspect client.media.attachments(post_id) and client.media.retrieve(media_id) for recovery. Never log upload response .data, capability URLs or tokens. The SDK redacts signed query tokens in HTTPX INFO logging; applications must not enable custom header/body logging of secrets.
Low-level APIs are also available: client.media.reserve_uploads(post_id, items=..., if_match=..., idempotency_key=...), client.media.attachments(...), client.media.retrieve(...).
Publish immediately
import os
from veypost import Veypost
with Veypost(
api_key=os.environ["VEYPOST_API_KEY"],
base_url=os.environ["VEYPOST_API_URL"],
workspace_id=os.environ.get("VEYPOST_WORKSPACE_ID"),
) as client:
post_id = os.environ["VEYPOST_POST_ID"]
validation = client.posts.validate(post_id)
if not validation.data["can_publish_now"]:
raise RuntimeError("Review validation issues")
current = client.posts.retrieve(post_id)
receipt = client.posts.publish(
post_id,
if_match=current.etag,
confirm_live_publish=True,
idempotency_key=os.environ["VEYPOST_COMMAND_ID"],
)
status = client.publication_requests.retrieve(post_id, receipt.data["id"])
print(status.data["request"]["state"])
print(status.data["post"]["deliveries"])
A 202 means accepted, not published. Worker execution, provider permissions, account/format readiness and subscription quotas remain authoritative. The SDK does not repeatedly submit publication to wait for success. Inspect request/post/delivery states, including partial failures and unknown provider outcomes.
Schedule a post
import os
from veypost import Veypost
with Veypost(
api_key=os.environ["VEYPOST_API_KEY"],
base_url=os.environ["VEYPOST_API_URL"],
workspace_id=os.environ.get("VEYPOST_WORKSPACE_ID"),
) as client:
post_id = os.environ["VEYPOST_POST_ID"]
validation = client.posts.validate(post_id)
if not validation.data["can_schedule"]:
raise RuntimeError("Review validation issues")
current = client.posts.retrieve(post_id)
receipt = client.posts.schedule(
post_id,
local_datetime="2030-11-03T10:00:00",
timezone="Europe/Paris",
fold=0,
if_match=current.etag,
idempotency_key=os.environ["VEYPOST_COMMAND_ID"],
)
print(receipt.data["id"], receipt.data["state"])
Schedule values are local wall-clock time plus an IANA timezone; the server rejects past times and daylight-saving gaps. fold selects the occurrence of an ambiguous time. Validate can_schedule first.
Withdrawal removes an eligible pending request. cancel_schedule cancels eligible scheduled post/delivery work. Deleting an eligible post is a separate command and returns .data is None for HTTP 204. These do not promise remote deletion of already-published social posts. Work already submitting/reconciling may not be cancellable. To change a pending schedule, withdraw its request, retrieve/review the draft and latest ETag, then submit a new schedule; the SDK does not disguise that multi-step process as an atomic reschedule.
Withdraw, cancel or delete
Set VEYPOST_ACTION to withdraw, cancel, or delete. Withdrawal also needs
VEYPOST_REQUEST_ID from the accepted request. Each revisioned change retrieves a
fresh ETag; review the post before cancelling or deleting it.
import os
from veypost import Veypost
with Veypost(
api_key=os.environ["VEYPOST_API_KEY"],
base_url=os.environ["VEYPOST_API_URL"],
workspace_id=os.environ.get("VEYPOST_WORKSPACE_ID"),
) as client:
post_id = os.environ["VEYPOST_POST_ID"]
# Choose one action, then inspect the returned state before another change.
action = os.environ["VEYPOST_ACTION"] # withdraw, cancel, or delete
if action == "withdraw":
result = client.publication_requests.withdraw(
post_id, os.environ["VEYPOST_REQUEST_ID"],
)
elif action == "cancel":
current = client.posts.retrieve(post_id)
result = client.posts.cancel_schedule(
post_id, if_match=current.etag,
idempotency_key=os.environ["VEYPOST_COMMAND_ID"],
)
elif action == "delete":
current = client.posts.retrieve(post_id)
result = client.posts.delete(post_id, if_match=current.etag)
else:
raise ValueError("Choose withdraw, cancel, or delete")
print(result.status_code, result.data)
Errors and safe retries
import os
from veypost import APIError, ConnectionError
from veypost import Veypost
with Veypost(
api_key=os.environ["VEYPOST_API_KEY"],
base_url=os.environ["VEYPOST_API_URL"],
workspace_id=os.environ.get("VEYPOST_WORKSPACE_ID"),
) as client:
try:
result = client.posts.create(
idempotency_key=os.environ["VEYPOST_COMMAND_ID"],
)
except APIError as error:
print(error.status_code, error.code, error.request_id)
except ConnectionError as error:
# The command may have been accepted. Keep error.idempotency_key.
# Inspect status or retry the identical command; do not publish again.
raise
Retryable 429/503 and transport failures are retried up to max_retries only for reads, validation or commands carrying an idempotency key. Retries preserve the exact serialized body, workspace, revision and key. A server Retry-After above 30 seconds is surfaced to the caller rather than shortened. State conflicts, malformed responses and unkeyed delete/withdraw commands are not automatically retried. Exceptions carry a generated idempotency key for recovery; caller-supplied persisted IDs are preferable for durable jobs. A bounded in-memory guard catches changed input for recently used command IDs; the server enforces durable idempotency. No API keys, request bodies or signed URLs are included in SDK exception messages.
Additional exceptions: ProtocolError, UploadError, WaitTimeout, all under VeypostError. HTTP response data is typed but is not a provider-success guarantee. Read timeouts bound individual network operations; complete workflow duration includes retries and processing.
Pagination
import os
from veypost import Veypost
with Veypost(
api_key=os.environ["VEYPOST_API_KEY"],
base_url=os.environ["VEYPOST_API_URL"],
workspace_id=os.environ.get("VEYPOST_WORKSPACE_ID"),
) as client:
for account in client.accounts.iter_all(limit=50):
print(account["id"])
for post in client.posts.iter_all(status="scheduled"):
print(post["id"])
list() returns one page; iter_all() lazily follows cursors and rejects repeated cursors. The key and owner plan share normal API/dashboard/MCP quotas.
Runnable examples
Examples default to http://localhost:8000 unless VEYPOST_API_URL is set. All mutation examples require --execute; commands also require a caller-supplied --command-id where applicable. They do not run during installation.
python packages/python-sdk/examples/discover.py
python packages/python-sdk/examples/create_draft.py --help
python packages/python-sdk/examples/upload_media.py --help
python packages/python-sdk/examples/publish.py --help
python packages/python-sdk/examples/schedule.py --help
python packages/python-sdk/examples/manage_post.py --help
Development and contract maintenance
uv sync --project packages/python-sdk --extra dev
uv run --project packages/python-sdk pytest packages/python-sdk/tests
python packages/python-sdk/scripts/generate_contract.py --check
uv build --project packages/python-sdk --out-dir /tmp/veypost-sdk-dist
After an intentional OpenAPI change, run python packages/python-sdk/scripts/generate_contract.py, review generated models/contract, and update adapters/examples/tests. The SDK has no backend application import or runtime dependency on repository paths. Tests substitute API and storage transports; no real provider publishing or hosted acceptance is implied.
Metadata
Release files for veypost 0.1.1
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 |
|---|---|---|---|---|
| veypost-0.1.1.tar.gz | 57.0 kB | With provenance | Details for veypost-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| veypost-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for veypost-0.1.1-py3-none-any.whl |
Total release size: 84.0 kB
Release files / veypost-0.1.1.tar.gz
| Download URL | veypost-0.1.1.tar.gz |
|---|---|
| Size | 57.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dbf75c1112834c60cf1cadfe9f01885ef9bb5fed6d0ce48aa2c578b5c7c99b6a
|
|
BLAKE2b-256 checksum How to use checksums |
34290ffae3cd9d34598ca82639aa667ababeaf8334dc3e35bf8e174b6454e9ff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logRelease files / veypost-0.1.1-py3-none-any.whl
| Download URL | veypost-0.1.1-py3-none-any.whl |
|---|---|
| Size | 27.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9d53ffcada72174e1a7ecdf269e410e036aa9d0515f0f7eb1617c6c9fc4d7a25
|
|
BLAKE2b-256 checksum How to use checksums |
9a9eebae378213bba6aef5b9c8aafd2913a11a61197901394c24725e85e8943a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logSecurity for veypost 0.1.1
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for veypost-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logA 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.