Python

Pine Labs Server SDK for Python.

pinelabs-python is the official Python SDK for the Pine Labs Online Payment Gateway (Plural). It is generated from the same OpenAPI spec that powers this documentation, so every operation, request, and response model is fully typed.

  • Package: pinelabs-python on PyPI
  • Runtime: Python ≥ 3.8
  • Type hints: Full PEP 561 type information ships with the package

Install

Bash
pip install pinelabs-python

Quickstart

The Pine Labs API uses OAuth2 with the client_credentials grant. Exchange your <client-id> / <client-secret> for an access token, then pass it to PinelabsApi.

Python
from pinelabs import Amount, PinelabsApi
 
base_url = "https://pluraluat.v2.pinepg.in"  # UAT
# base_url = "https://api.pluralpay.in"      # Production
 
# 1. Get a token. The /token endpoint does not require authentication, so we
#    pass token="" on this bootstrap client.
auth = PinelabsApi(base_url=base_url, 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=base_url,
    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 the API reference for the full schema
)
 
print(order)

Environments

EnvironmentBase URL
UAThttps://pluraluat.v2.pinepg.in
Productionhttps://api.pluralpay.in

Pass the URL via base_url. The exported PinelabsApiEnvironment enum currently only contains PRODUCTION; for UAT, set base_url explicitly.

Python
from pinelabs import PinelabsApi
from pinelabs.environment import PinelabsApiEnvironment
 
client = PinelabsApi(
    token="<access-token>",
    environment=PinelabsApiEnvironment.PRODUCTION,
)

Auto-refreshing token

For long-running services, cache the token and refresh it before it expires. Pass a callable instead of a string to token — it is invoked on every request. Use a separate bootstrap auth client (without a supplier) to fetch the token, so there is no risk of recursing into the supplier from within itself.

Python
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:
        # Refresh ~30s before expiry
        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)
 
# Every call now picks up a fresh token automatically.
client.orders.get_order_by_id(order_id="ord-123")
Use a separate bootstrap client

Always create a dedicated auth client (with token="") to fetch tokens. Calling generate_token() on a client that itself uses the supplier callable will recurse and hang.

Async client

The SDK also exports an async variant for non-blocking use cases (FastAPI, asyncio workers). If you pass a custom httpx_client, use httpx.AsyncClient() instead of httpx.Client().

Python
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-clientPurpose
client.authenticationOAuth token generation
client.ordersCreate, capture, cancel, and fetch orders
client.refundsCreate and look up refunds
client.settlementsSettlement reports + UTR lookup
client.checkoutHosted-checkout related operations
client.payment_linksSingle + bulk payment links
client.card_paymentsDirect card payment + OTP flow
client.bnplBuy-Now-Pay-Later eligibility and flows
client.convenience_feeConvenience-fee config and computation
client.e_challansGovernment e-challan integration
client.apple_payApple Pay session + decryption
client.international_paymentsCross-border (DCC / MCC) payments
client.customersCustomer profile management
client.tokenizationCard / network tokenization
client.payoutsPayouts: balance, create, cancel, list
client.subscriptions_plansRecurring-billing plans
client.subscriptions_subscriptionsSubscription lifecycle
client.subscriptions_presentationsSubscription debit presentations
client.pay_by_pointsLoyalty / points-based payments
client.affordability_suiteEMI / offer eligibility
client.split_settlementsSplit settlements between sub-merchants

For the full operation list and request/response schemas, see the API reference.

Recipes

Create a payment link

Python
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

Python
order = client.orders.get_order_by_id(order_id="v1-241010055924-aa-AHbN0s")
print(order)

Refund an order

Python
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 so you can handle specific failures cleanly.

Python
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)

ApiError exposes status_code, body (parsed JSON when the server returns JSON), and headers.

Per-request options

Every operation accepts a request_options keyword argument for ad-hoc overrides:

Python
client.orders.create_order(
    merchant_order_reference="order-002",
    order_amount=Amount(value=1000, currency="INR"),
    request_options={
        "timeout_in_seconds": 10,
        "max_retries": 0,
        "additional_headers": {"x-correlation-id": "req-abc"},
    },
)

Useful for per-call timeouts, propagating trace IDs, or disabling retries on idempotency-sensitive flows.

Retries

The SDK retries with exponential backoff (default: 2 retries) on 408, 429, and 5xx responses. Override per request via max_retries, or globally via the request_options argument on any call.

Timeouts

The default timeout is 60 seconds. Override at the client or per-request level:

Python
client = PinelabsApi(token="<access-token>", timeout=20.0)

Custom HTTP client

Python
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

The package ships full PEP 561 type information. Request/response models live under pinelabs.types; resource-specific request models live under each sub-package (e.g. pinelabs.refunds.CreateRefundRequestOrderAmount). The codebase passes mypy --strict.

Source & support

For the agent-toolkit (LangChain, Vercel AI SDK, OpenAI Agents, Anthropic, Claude Agent SDK), see pinelabs-agent-toolkit. For the command-line interface, see pinelabs-cli.

New chat
Responses are generated using AI and may contain mistakes.
Hi! I'm Pine, your AI developer assistant. Ask me anything about Pine Labs APIs, integrations, or troubleshooting.

Tip: you can create a new chat with + E