SDKs / Python
Python SDK
Install pantech from PyPI, create a client, make a machine and wait for it, and handle errors, pages and retries.
import pantech
client = pantech.Pantech()
print(client.me.get()["organization_id"])pantech is the official Python library. It needs Python 3.9 or later, and its only dependency is httpx.
Install
$ pip install pantechAuthenticate
Make an API key and put it in PANTECH_API_KEY. The client reads it.
$ export PANTECH_API_KEY=PAN_…Or pass it, with an optional base URL (also read from PANTECH_API_URL):
import os
import pantech
client = pantech.Pantech(
api_key=os.environ["MY_PANTECH_KEY"],
base_url="https://api.pantechdynamics.com/public/v1",
max_retries=5, # default 3
)The other arguments are timeout (an httpx.Timeout, default 60 seconds), default_headers, user_agent and http_client (your own httpx.Client). Use the client as a context manager, or call client.close(), to close its connections.
Create a machine and wait for it
import pantech
client = pantech.Pantech()
order = client.instances.create(
name="web-01",
plan_slug="starter",
image_slug="ubuntu-24-04",
ssh_key_id="sshk_…",
)
# Creating a machine places an order: payment, then provisioning.
# wait_for_order returns the order once it is provisioned.
done = client.instances.wait_for_order(order["order_id"])
instance = client.instances.get(done["instance_id"])
print(instance["name"], instance["public_ipv4"])wait_for_order raises pantech.OrderFailedError if the payment or the order fails, and pantech.WaitTimeoutError after 15 minutes (pass timeout= in seconds to change it). Databases have the same client.databases.wait_for_order.
Read real slugs from the catalogue first: client.catalog.list_all_plans(), list_all_images(), list_all_regions() and list_all_disk_offerings().
Resources
Each part of the API is an attribute of the client:
client.instances, client.networks, client.public_ips, client.security_groups, client.load_balancers, client.volumes, client.snapshots, client.databases, client.kubernetes, client.ssh_keys, client.webhooks, client.operations, client.catalog, client.billing, client.usage and client.me.
Families nest where the API does, for example client.networks.subnets, client.networks.firewall_rules, client.public_ips.port_forwarding_rules and client.kubernetes.node_pools. Responses are plain dictionaries, typed with TypedDicts from pantech.types.
Async
pantech.AsyncPantech has the same methods, to await:
import asyncio
import pantech
async def main():
async with pantech.AsyncPantech() as client:
me = await client.me.get()
print(me["organization_id"])
asyncio.run(main())In the async client, list_all returns an async iterator: async for instance in client.instances.list_all():.
Errors
Every API error is a pantech.APIError, with status, code, title, detail, request_id and field errors. Common ones have their own class:
| Class | When |
|---|---|
BadRequestError | 400 |
AuthenticationError | 401: the key is wrong, expired or revoked |
PaymentRequiredError | 402: not enough credit for this order |
PermissionDeniedError | 403: the key's scope or its creator's role does not allow it, or AI apps are turned off |
NotFoundError | 404 |
ConflictError | 409 |
ValidationError | 422: see errors for each bad field |
RateLimitError | 429, after the retries ran out; retry_after is the wait asked for |
InternalServerError | 5xx, after the retries ran out |
Errors that are not an API answer: APIConnectionError (the request never got an answer) and its subclass APITimeoutError, OperationFailedError and OrderFailedError (a wait ended in failure), WaitTimeoutError and WebhookVerificationError. All of them, APIError included, are a pantech.PantechError.
try:
client.instances.get("vm_…")
except pantech.NotFoundError:
print("no such machine")
except pantech.APIError as e:
print(e.code, e.detail, e.request_id)Act on code, never the wording. Every code.
Pages
list() returns one page: a dictionary with data and next_cursor. list_all() loops over every item and follows next_cursor for you:
for instance in client.instances.list_all():
print(instance["name"])Operations
Most changes return an operation. Wait for it before you rely on the result:
accepted = client.instances.stop("vm_…")
op = client.operations.wait(accepted["operation_id"])
print(op["status"]) # "succeeded"wait polls GET /operations/{id} every 2 seconds. It raises pantech.OperationFailedError (with code and reason) if the operation fails, unless you pass raise_on_failure=False, and pantech.WaitTimeoutError after 10 minutes (timeout= in seconds, None to wait for ever).
Idempotency and retries
Every POST, PUT, PATCH and DELETE gets an Idempotency-Key automatically. The same key is sent on every retry, so the change happens once. To choose it yourself, pass idempotency_key= to any write method.
The client retries network errors, timeouts, 429 (after Retry-After), 500, 502, 503 and 504, up to 3 times, with a growing, random wait. Calls the API cannot replay safely, such as adding an SSH key, are sent once, and only a 429 is retried. The list.
Calling any endpoint
client.request calls a path directly, with the same auth, retries and errors, and returns the parsed body:
usage = client.request("GET", "/usage")It also takes query=, body=, idempotency_key=, extra_headers=, timeout= and max_retries=. Every typed method takes extra_headers=, extra_query= and timeout= too.
Webhooks
pantech.verify_webhook(secret, header, body) checks a delivery's signature. It returns None, and raises pantech.WebhookVerificationError if the signature does not verify. Verify webhooks with the SDKs.