pinelabs-python
Official Python SDK for the Pine Labs Online Payment Gateway (Plural).
Install
pip install pinelabs-python
# or
poetry add pinelabs-python
# or
uv add pinelabs-python
Requires Python ≥ 3.8.
Quickstart
The Pine Labs API uses OAuth2 client_credentials. Exchange your <client-id> / <client-secret> for an access token, then pass it to PinelabsApi.
from pinelabs import Amount, PinelabsApi
# 1. Get a token. The /token endpoint does not require authentication, so we
# pass token="" on this bootstrap client.
auth = PinelabsApi(
base_url="https://pluraluat.v2.pinepg.in", # or "https://api.pluralpay.in" for prod
token="",
)
token_response = auth.authentication.generate_token(
grant_type="client_credentials",
client_id="<client-id>",
client_secret="<client-secret>",
)
# 2. Build an authenticated client
client = PinelabsApi(
base_url="https://pluraluat.v2.pinepg.in",
token=token_response.access_token,
)
# 3. Call any operation
order = client.orders.create_order(
merchant_order_reference="order-001",
order_amount=Amount(value=50000, currency="INR"), # ₹500.00
# ...see API reference for the full schema
)
print(order)
For long-running processes, refresh the token before it expires using a token-supplier callable (see Auto-refreshing token below).
Environments
| Environment | Base URL |
|---|---|
| UAT | https://pluraluat.v2.pinepg.in |
| Production | https://api.pluralpay.in |
Pass the URL via base_url. The PinelabsApiEnvironment enum currently exposes only PRODUCTION; for UAT, set base_url explicitly.
from pinelabs import PinelabsApi
from pinelabs.environment import PinelabsApiEnvironment
client = PinelabsApi(
token="<access-token>",
environment=PinelabsApiEnvironment.PRODUCTION,
)
Auto-refreshing token
Pass a token callable instead of a string. The SDK invokes it on every request. Because the bootstrap auth client below does not use the supplier, there is no re-entrancy concern.
import time
import threading
from typing import Optional
from pinelabs import PinelabsApi
base_url = "https://pluraluat.v2.pinepg.in"
# Bootstrap client used only to fetch tokens. It has no supplier, so calling
# generate_token() from inside get_token() will not recurse.
auth = PinelabsApi(base_url=base_url, token="")
_cached: Optional[dict] = None
_lock = threading.Lock()
def get_token() -> str:
global _cached
with _lock:
if _cached and time.time() < _cached["expires_at"] - 30:
return _cached["value"]
r = auth.authentication.generate_token(
grant_type="client_credentials",
client_id="<client-id>",
client_secret="<client-secret>",
)
_cached = {
"value": r.access_token,
"expires_at": time.time() + r.expires_in,
}
return _cached["value"]
client = PinelabsApi(base_url=base_url, token=get_token)
Async client
The SDK also exports an async client for non-blocking calls. If you pass a custom httpx_client, use httpx.AsyncClient() instead of httpx.Client().
import asyncio
from pinelabs import Amount, AsyncPinelabsApi
client = AsyncPinelabsApi(token="<access-token>")
async def main() -> None:
order = await client.orders.create_order(
merchant_order_reference="order-001",
order_amount=Amount(value=50000, currency="INR"),
)
print(order)
asyncio.run(main())
Sub-clients
PinelabsApi exposes one sub-client per API tag:
| Sub-client | Purpose |
|---|---|
client.authentication |
OAuth token generation |
client.orders |
Create / capture / cancel / fetch orders |
client.refunds |
Create and look up refunds |
client.settlements |
Settlement reports + UTR lookup |
client.checkout |
Hosted-checkout related operations |
client.payment_links |
Single + bulk payment links |
client.card_payments |
Direct card payment + OTP |
client.bnpl |
Buy-Now-Pay-Later flows |
client.convenience_fee |
Convenience-fee config + computation |
client.e_challans |
Government e-challan integration |
client.apple_pay |
Apple Pay session + decryption |
client.international_payments |
Cross-border payments |
client.customers |
Customer profile management |
client.tokenization |
Card / network tokenization |
client.payouts |
Payouts: balance, create, cancel, list |
client.subscriptions_plans |
Recurring-billing plans |
client.subscriptions_subscriptions |
Subscription lifecycle |
client.subscriptions_presentations |
Subscription debit presentations |
client.pay_by_points |
Loyalty / points-based payments |
client.affordability_suite |
EMI / offer eligibility |
client.split_settlements |
Split settlements between sub-merchants |
For the full operation list, see the API reference.
Recipes
Create a payment link
from pinelabs import Amount, PinelabsApi
client = PinelabsApi(token="<access-token>")
link = client.payment_links.create_payment_link(
merchant_payment_link_reference="link-001",
amount=Amount(value=50000, currency="INR"),
description="Order #001",
)
print(link)
Fetch an order
order = client.orders.get_order_by_id(order_id="v1-241010055924-aa-AHbN0s")
print(order)
Refund an order
from pinelabs.refunds import CreateRefundRequestOrderAmount
refund = client.refunds.create_refund(
order_id="v1-241010055924-aa-AHbN0s",
merchant_order_reference="refund-001",
order_amount=CreateRefundRequestOrderAmount(value=400, currency="INR"),
)
print(refund)
Error handling
All HTTP errors derive from pinelabs.core.api_error.ApiError. The SDK also exposes typed subclasses per status code:
from pinelabs import PinelabsApi
from pinelabs.core.api_error import ApiError
from pinelabs.errors import (
BadRequestError,
UnauthorizedError,
ForbiddenError,
NotFoundError,
ConflictError,
UnprocessableEntityError,
InternalServerError,
ServiceUnavailableError,
)
client = PinelabsApi(token="<access-token>")
try:
client.orders.get_order_by_id(order_id="missing")
except NotFoundError as e:
print("order does not exist:", e.body)
except UnauthorizedError:
print("token expired or invalid")
except ApiError as e:
print("HTTP", e.status_code, e.body)
Per-request options
Every operation accepts a request_options keyword argument:
client.orders.create_order(
merchant_order_reference="order-001",
order_amount=Amount(value=50000, currency="INR"),
request_options={
"timeout_in_seconds": 30,
"max_retries": 2,
"additional_headers": {"x-correlation-id": "abc"},
},
)
Retries
The SDK retries with exponential backoff (default: 2 retries) on:
408(Request Timeout)429(Too Many Requests)5xx(Server errors)
Timeouts
The default timeout is 60 seconds. Override at the client or per-request level:
client = PinelabsApi(token="<access-token>", timeout=20.0)
Access raw response data
response = client.authentication.with_raw_response.generate_token(
grant_type="client_credentials",
client_id="<client-id>",
client_secret="<client-secret>",
)
print(response.headers)
print(response.data)
Custom HTTP client
import httpx
from pinelabs import PinelabsApi
client = PinelabsApi(
token="<access-token>",
httpx_client=httpx.Client(
proxy="http://my.test.proxy.example.com",
transport=httpx.HTTPTransport(local_address="0.0.0.0"),
),
)
Type hints
Full type hints (PEP 561) ship with the package. Request/response models live under pinelabs.types; resource-specific request models live under each sub-package (e.g. pinelabs.refunds.CreateRefundRequestOrderAmount).
License
MIT
Metadata
Release files for pinelabs-python 0.2.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 | |
|---|---|---|---|
| pinelabs_python-0.2.1.tar.gz | 170.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pinelabs_python-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 685.0 kB
Release files / pinelabs_python-0.2.1.tar.gz
| Download URL | pinelabs_python-0.2.1.tar.gz |
|---|---|
| Size | 170.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
714272d39b3294792c1281bef57a50598a3667e181ccc34497f67fee90efde45
|
|
BLAKE2b-256 checksum How to use checksums |
f61947f329ab34840afacc61a14b18e1ff345df45f4209a30cab2ea316b0bb11
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 May 21, 2026.
Transparency logRelease files / pinelabs_python-0.2.1-py3-none-any.whl
| Download URL | pinelabs_python-0.2.1-py3-none-any.whl |
|---|---|
| Size | 515.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3f0804fc002b0448c98c9de43e92c5d9bae78957164a324db86f8c1d5c2e41e4
|
|
BLAKE2b-256 checksum How to use checksums |
60bc507412234cab782ab9cc1adcf35db0121938b209b237d41b4d237ab083b7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 May 21, 2026.
Transparency log