Python
Uses PyJWT with the cryptography backend, and requests.
pip install "PyJWT[crypto]" requests
Token client
ryno_token.py
import os
import time
import uuid
import jwt
import requests
CLIENT_ID = os.environ["RYNO_CLIENT_ID"] # from the API Credentials page
TOKEN_URL = os.environ["RYNO_TOKEN_URL"] # e.g. https://keycloak.sandbox.rynopay.io/realms/ryno/protocol/openid-connect/token
with open(os.environ["RYNO_PRIVATE_KEY_PATH"], "rb") as f:
PRIVATE_KEY = f.read()
_cached = {"token": None, "expires_at": 0.0}
def build_assertion() -> str:
"""Sign a single-use client assertion, valid for 60 seconds."""
now = int(time.time())
claims = {
"iss": CLIENT_ID,
"sub": CLIENT_ID,
"aud": TOKEN_URL,
"jti": str(uuid.uuid4()),
"iat": now,
"exp": now + 60,
}
return jwt.encode(claims, PRIVATE_KEY, algorithm="RS256", headers={"typ": "JWT"})
def get_access_token(scope: str = "partner.read partner.write") -> str:
"""Return a cached access token, requesting a new one 30 seconds before expiry."""
if _cached["token"] and time.time() < _cached["expires_at"] - 30:
return _cached["token"]
response = requests.post(
TOKEN_URL,
data={
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": build_assertion(),
"scope": scope,
},
timeout=10,
)
body = response.json()
if not response.ok:
raise RuntimeError(f"Token request failed: {body.get('error')} {body.get('error_description', '')}")
_cached["token"] = body["access_token"]
_cached["expires_at"] = time.time() + body["expires_in"]
return _cached["token"]
Call the API
example.py
import requests
from ryno_token import get_access_token
API_BASE_URL = "https://gateway.sandbox.rynopay.io/api"
response = requests.get(
f"{API_BASE_URL}/partner/v1/business-profiles",
params={"page": 1, "pageSize": 20},
headers={"Authorization": f"Bearer {get_access_token()}"},
timeout=10,
)
print(response.status_code, response.json())