Official Python SDK for the Postpin shipping-rate API
Project description
postpin
Official Python SDK for the Postpin shipping-rate API — calculate courier charges between Indian pincodes, check serviceability, look up the pincode directory, and verify webhooks.
- Fully typed — dataclass results and typed parameters; ships
py.typed. - Robust by default — automatic retries with exponential backoff + jitter,
Retry-Aftersupport, per-request timeouts, and idempotency keys. - Typed errors — catch
RateLimitError,QuotaExceededError,ValidationError, and friends. - Secure webhooks — constant-time HMAC signature verification with replay protection.
Requirements
- Python 3.8+
Install
pip install postpin
Quick start
import os
from postpin import Postpin
client = Postpin(os.environ["POSTPIN_API_KEY"])
rate = client.rates.calculate(
origin="400001", # Mumbai
destination="110001", # New Delhi
weight=1200, # grams
service="express", # "surface" | "express" | "same_day"
)
print(rate.total, rate.currency) # 254.38 INR
print(rate.eta_days) # [1, 3]
for line in rate.breakdown:
print(line.label, line.amount)
Load your API key from an environment variable — never hard-code it.
The client can be used as a context manager to close the connection pool:
with Postpin(api_key) as client:
rate = client.rates.calculate(origin="400001", destination="781001", weight=2500)
Configuration
client = Postpin(
api_key,
base_url="https://api.postpin.in/v1", # override the API base (incl. version)
timeout=30.0, # per-request timeout in seconds
max_retries=2, # retries on 429/5xx/network errors
headers={"x-team": "logistics"}, # extra headers on every request
)
Resources
# Rates
rate = client.rates.calculate(
origin="400001", destination="781001", weight=2500,
length=30, width=20, height=15, # cm — enables volumetric weight
cod=True, declared_value=4999, # rupees
)
# Serviceability
check = client.serviceability.check("781001")
print(check.serviceable, check.city)
# Pincodes
pin = client.pincodes.get("302001")
states = client.pincodes.states()
# Plans
plans = client.plans.list()
Every method accepts timeout=, and rates.calculate also accepts idempotency_key=.
Error handling
from postpin import (
Postpin, RateLimitError, QuotaExceededError,
ValidationError, AuthenticationError, NotFoundError,
)
try:
client.rates.calculate(origin="400001", destination="110001", weight=1200)
except RateLimitError as e:
print(f"Rate limited — retry after {e.retry_after}s")
except QuotaExceededError:
print("Monthly quota exhausted — upgrade your plan")
except ValidationError as e:
print("Bad request:", e.message, e.details)
except AuthenticationError:
print("Invalid API key")
| Error | HTTP | When |
|---|---|---|
AuthenticationError |
401 | Missing or invalid API key |
PermissionDeniedError |
403 | Key lacks the required scope |
ValidationError |
400 / 422 | Invalid parameters (.details) |
NotFoundError |
404 | Resource does not exist |
QuotaExceededError |
402 | Monthly quota used up |
RateLimitError |
429 | Too many requests (.retry_after) |
APIError |
5xx | Server-side error (auto-retried) |
APITimeoutError |
— | Request exceeded timeout |
APIConnectionError |
— | Network failure after retries |
Every PostpinError carries code, status_code, request_id, details, and headers.
Webhooks
Verify against the raw request body — re-serializing the JSON breaks the signature.
from flask import Flask, request
from postpin import webhooks, SignatureVerificationError
app = Flask(__name__)
@app.post("/webhooks/postpin")
def handle():
try:
event = webhooks.construct_event(
request.get_data(), # raw bytes
request.headers.get("X-Postpin-Signature"),
os.environ["POSTPIN_WEBHOOK_SECRET"],
)
except SignatureVerificationError:
return "", 400
if event["event"] == "rate.calculated":
... # handle event["data"]
return "", 200
verify() returns True or raises; construct_event() verifies and returns the parsed event. Both accept tolerance_seconds= (default 300; 0 disables the replay check).
License
MIT © Postpin
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 postpin-0.1.0.tar.gz.
File metadata
- Download URL: postpin-0.1.0.tar.gz
- Upload date:
- Size: 13.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca340ffc906ae7f4e2b91442e2ed06e848b68c7e3f38ae4cd074271732c85289
|
|
| MD5 |
3035ddd869422b1a04a3d53850ab7b8d
|
|
| BLAKE2b-256 |
73df44677cbbe838675321aa6688d40ab883669829bfd4770dd8d0c20db6b6a0
|
Provenance
The following attestation bundles were made for postpin-0.1.0.tar.gz:
Publisher:
publish-python.yml on its-pradeependra/PostPin
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
postpin-0.1.0.tar.gz -
Subject digest:
ca340ffc906ae7f4e2b91442e2ed06e848b68c7e3f38ae4cd074271732c85289 - Sigstore transparency entry: 2161357824
- Sigstore integration time:
-
Permalink:
its-pradeependra/PostPin@8098520232719a96c24fbfeba43b01aba4faff21 -
Branch / Tag:
refs/tags/postpin-python/v0.1.0 - Owner: https://github.com/its-pradeependra
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@8098520232719a96c24fbfeba43b01aba4faff21 -
Trigger Event:
push
-
Statement type:
File details
Details for the file postpin-0.1.0-py3-none-any.whl.
File metadata
- Download URL: postpin-0.1.0-py3-none-any.whl
- Upload date:
- Size: 14.5 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 |
928da641d6b6c9ee8fe0a746688ccfbb38e7c48891497cc4e1feece6f905e292
|
|
| MD5 |
030b04035dfaf52873ee85caf3d8217d
|
|
| BLAKE2b-256 |
fb8df8989ab7fcc67343650ae9fcfb73f628ece09bbb3136f34baad53ee92c18
|
Provenance
The following attestation bundles were made for postpin-0.1.0-py3-none-any.whl:
Publisher:
publish-python.yml on its-pradeependra/PostPin
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
postpin-0.1.0-py3-none-any.whl -
Subject digest:
928da641d6b6c9ee8fe0a746688ccfbb38e7c48891497cc4e1feece6f905e292 - Sigstore transparency entry: 2161357885
- Sigstore integration time:
-
Permalink:
its-pradeependra/PostPin@8098520232719a96c24fbfeba43b01aba4faff21 -
Branch / Tag:
refs/tags/postpin-python/v0.1.0 - Owner: https://github.com/its-pradeependra
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@8098520232719a96c24fbfeba43b01aba4faff21 -
Trigger Event:
push
-
Statement type: