Skip to main content

Node.js

Uses jose to sign the client assertion. Requires Node.js 18 or later for the built-in fetch.

npm install jose

Token client​

ryno-token.js
import { SignJWT, importPKCS8 } from 'jose';
import { randomUUID } from 'node:crypto';
import { readFileSync } from 'node:fs';

const CLIENT_ID = process.env.RYNO_CLIENT_ID; // from the API Credentials page
const TOKEN_URL = process.env.RYNO_TOKEN_URL; // e.g. https://keycloak.sandbox.rynopay.io/realms/ryno/protocol/openid-connect/token
const PRIVATE_KEY = await importPKCS8(readFileSync(process.env.RYNO_PRIVATE_KEY_PATH, 'utf8'), 'RS256');

/** Signs a single-use client assertion. Valid for 60 seconds. */
export function buildAssertion() {
return new SignJWT({})
.setProtectedHeader({ alg: 'RS256', typ: 'JWT' })
.setIssuer(CLIENT_ID)
.setSubject(CLIENT_ID)
.setAudience(TOKEN_URL)
.setJti(randomUUID())
.setIssuedAt()
.setExpirationTime('60s')
.sign(PRIVATE_KEY);
}

let cached = null; // { token, expiresAt }

/** Returns a cached access token, requesting a new one 30 seconds before expiry. */
export async function getAccessToken(scope = 'partner.read partner.write') {
if (cached && Date.now() < cached.expiresAt - 30_000) return cached.token;

const response = await fetch(TOKEN_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: CLIENT_ID,
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
client_assertion: await buildAssertion(),
scope,
}),
});

const body = await response.json();
if (!response.ok) {
throw new Error(`Token request failed: ${body.error} ${body.error_description ?? ''}`);
}

cached = { token: body.access_token, expiresAt: Date.now() + body.expires_in * 1000 };
return cached.token;
}

Call the API​

example.js
import { getAccessToken } from './ryno-token.js';

const API_BASE_URL = 'https://gateway.sandbox.rynopay.io/api';

const response = await fetch(`${API_BASE_URL}/partner/v1/business-profiles?page=1&pageSize=20`, {
headers: { Authorization: `Bearer ${await getAccessToken()}` },
});

console.log(response.status, await response.json());