Oplyon Partner API
Leggi in sola lettura i dati gestionali di un tenant Oplyon, con il consenso esplicito del titolare — tramite OAuth 2.0 con PKCE (RFC 7636).
Come funziona il flusso
client_id e client_secret — un'unica coppia dedicata alla tua applicazione, non condivisa con altri sviluppatori./connect/authorize del suo tenant. PKCE è obbligatorio per tutti i client, anche confidenziali.GET https://<tenant>.oplyon.com/connect/authorize ?response_type=code &client_id=IL_TUO_CLIENT_ID &redirect_uri=IL_TUO_REDIRECT_URI &scope=oplyon.products.read offline_access &state=VALORE_CASUALE_ANTI_CSRF &code_challenge=SHA256_BASE64URL(code_verifier) &code_challenge_method=S256
redirect_uri con ?code=...&state=..../connect/token con il code, le tue credenziali e il code_verifier PKCE originale. Ricevi un access_token (JWT, 15 minuti) e un refresh_token (30 giorni, a rotazione).POST https://<tenant>.oplyon.com/connect/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=IL_CODE_RICEVUTO &redirect_uri=IL_TUO_REDIRECT_URI &client_id=IL_TUO_CLIENT_ID &client_secret=IL_TUO_CLIENT_SECRET &code_verifier=IL_CODE_VERIFIER_ORIGINALE
Bearer a qualunque endpoint /api/partner/v1/....GET https://<tenant>.oplyon.com/api/partner/v1/products Authorization: Bearer IL_ACCESS_TOKEN
refresh_token con grant_type=refresh_token sullo stesso endpoint /connect/token per ottenere un nuovo access token senza richiedere di nuovo il consenso.Scope disponibili
Ogni scope dà accesso in sola lettura a una categoria di dati. Richiedi solo quelli che ti servono davvero.
Esempi di codice
Un client funzionante completo per uno scope — genera la coppia PKCE, costruisce l'URL di autorizzazione, scambia il code, chiama un endpoint e rinnova il token. Scegli il tuo stack.
1. Genera una coppia PKCE (una volta per tentativo di login):
# macOS/Linux
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=+/' | cut -c1-64)
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr -d '=' | tr '+/' '-_')
2. Apri questo URL in un browser — il titolare del tenant fa login e acconsente:
GET https://<tenant>.oplyon.com/connect/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=YOUR_REDIRECT_URI &scope=oplyon.products.read offline_access &state=RANDOM_ANTI_CSRF_VALUE &code_challenge=$CODE_CHALLENGE &code_challenge_method=S256
3. Il tuo redirect_uri riceve ?code=... — scambialo con i token:
curl -s -X POST https://<tenant>.oplyon.com/connect/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d grant_type=authorization_code \ -d code=THE_CODE_YOU_RECEIVED \ -d redirect_uri=YOUR_REDIRECT_URI \ -d client_id=YOUR_CLIENT_ID \ -d client_secret=YOUR_CLIENT_SECRET \ -d code_verifier=$CODE_VERIFIER
Risposta:
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "CfDJ8...",
"scope": "oplyon.products.read offline_access"
}
4. Chiama un endpoint:
curl -s https://<tenant>.oplyon.com/api/partner/v1/products?limit=50&offset=0 \
-H "Authorization: Bearer ACCESS_TOKEN"
5. Quando l'access token scade (15 min), rinnovalo — senza richiedere di nuovo il consenso:
curl -s -X POST https://<tenant>.oplyon.com/connect/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d grant_type=refresh_token \ -d refresh_token=REFRESH_TOKEN \ -d client_id=YOUR_CLIENT_ID \ -d client_secret=YOUR_CLIENT_SECRET
Node.js 18+ (fetch e crypto integrati, nessuna dipendenza):
import crypto from "node:crypto"; const TENANT = "<tenant>.oplyon.com"; const CLIENT_ID = "YOUR_CLIENT_ID"; const CLIENT_SECRET = "YOUR_CLIENT_SECRET"; const REDIRECT_URI = "YOUR_REDIRECT_URI"; function base64url(buf) { return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } // 1. PKCE pair — store codeVerifier server-side, keyed by state, until the callback arrives const codeVerifier = base64url(crypto.randomBytes(32)); const codeChallenge = base64url(crypto.createHash("sha256").update(codeVerifier).digest()); const state = base64url(crypto.randomBytes(16)); // 2. Redirect the user's browser to this URL const authorizeUrl = new URL(`https://${TENANT}/connect/authorize`); authorizeUrl.search = new URLSearchParams({ response_type: "code", client_id: CLIENT_ID, redirect_uri: REDIRECT_URI, scope: "oplyon.products.read offline_access", state, code_challenge: codeChallenge, code_challenge_method: "S256", }).toString(); // 3. In your redirect_uri handler, exchange the received ?code=... for tokens async function exchangeCodeForTokens(code) { const res = await fetch(`https://${TENANT}/connect/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: REDIRECT_URI, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, code_verifier: codeVerifier, }), }); if (!res.ok) throw new Error(`token exchange failed: ${res.status}`); return await res.json(); // { access_token, refresh_token, expires_in, ... } } // 4. Call an endpoint with the access token async function listProducts(accessToken, { limit = 50, offset = 0 } = {}) { const url = `https://${TENANT}/api/partner/v1/products?limit=${limit}&offset=${offset}`; const res = await fetch(url, { headers: { Authorization: `Bearer ${accessToken}` } }); if (res.status === 401) throw new Error("access token expired or invalid — refresh it"); if (!res.ok) throw new Error(`API call failed: ${res.status}`); return await res.json(); // { items: [...], total, limit, offset } } // 5. Refresh when the access token expires (no new consent needed) async function refreshAccessToken(refreshToken) { const res = await fetch(`https://${TENANT}/connect/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, }), }); if (!res.ok) throw new Error(`refresh failed: ${res.status}`); return await res.json(); }
.NET 8 / HttpClient:
using System.Security.Cryptography; using System.Text; using System.Text.Json; const string Tenant = "<tenant>.oplyon.com"; const string ClientId = "YOUR_CLIENT_ID"; const string ClientSecret = "YOUR_CLIENT_SECRET"; const string RedirectUri = "YOUR_REDIRECT_URI"; static string Base64Url(byte[] bytes) => Convert.ToBase64String(bytes).Replace('+', '-').Replace('/', '_').TrimEnd('='); // 1. PKCE pair — keep codeVerifier server-side, keyed by state, until the callback arrives var codeVerifier = Base64Url(RandomNumberGenerator.GetBytes(32)); var codeChallenge = Base64Url(SHA256.HashData(Encoding.ASCII.GetBytes(codeVerifier))); var state = Base64Url(RandomNumberGenerator.GetBytes(16)); // 2. Redirect the user's browser here var authorizeUrl = $"https://{Tenant}/connect/authorize" + $"?response_type=code&client_id={ClientId}&redirect_uri={Uri.EscapeDataString(RedirectUri)}" + $"&scope=oplyon.products.read+offline_access&state={state}" + $"&code_challenge={codeChallenge}&code_challenge_method=S256"; using var http = new HttpClient(); // 3. In your redirect_uri handler, exchange ?code=... for tokens async Task<JsonDocument> ExchangeCodeAsync(string code) { var form = new Dictionary<string, string> { ["grant_type"] = "authorization_code", ["code"] = code, ["redirect_uri"] = RedirectUri, ["client_id"] = ClientId, ["client_secret"] = ClientSecret, ["code_verifier"] = codeVerifier, }; var res = await http.PostAsync($"https://{Tenant}/connect/token", new FormUrlEncodedContent(form)); res.EnsureSuccessStatusCode(); return JsonDocument.Parse(await res.Content.ReadAsStringAsync()); } // 4. Call an endpoint with the access token async Task<JsonDocument> ListProductsAsync(string accessToken, int limit = 50, int offset = 0) { using var req = new HttpRequestMessage(HttpMethod.Get, $"https://{Tenant}/api/partner/v1/products?limit={limit}&offset={offset}"); req.Headers.Authorization = new("Bearer", accessToken); var res = await http.SendAsync(req); res.EnsureSuccessStatusCode(); // 401 here means the access token expired — refresh it return JsonDocument.Parse(await res.Content.ReadAsStringAsync()); // { items, total, limit, offset } } // 5. Refresh when the access token expires (no new consent needed) async Task<JsonDocument> RefreshAsync(string refreshToken) { var form = new Dictionary<string, string> { ["grant_type"] = "refresh_token", ["refresh_token"] = refreshToken, ["client_id"] = ClientId, ["client_secret"] = ClientSecret, }; var res = await http.PostAsync($"https://{Tenant}/connect/token", new FormUrlEncodedContent(form)); res.EnsureSuccessStatusCode(); return JsonDocument.Parse(await res.Content.ReadAsStringAsync()); }
Ogni endpoint di lista accetta i parametri limit (massimo 100, default 50) e offset e restituisce { items, total, limit, offset }. Un 401 significa che l'access token manca, è scaduto o non è valido — rinnovalo. Un 403 significa che il token è valido ma non ha lo scope richiesto, oppure il tenant del token non corrisponde all'host. Gli endpoint che espongono un singolo record accettano un id in rotta, es. GET /products/{id}, e restituiscono 404 se non trovato.
Note importanti
redirect_uri deve corrispondere esattamente (byte per byte) a uno di quelli registrati per la tua app.Pronto a costruire la tua integrazione?
Raccontaci cosa stai costruendo — revisioneremo la richiesta e ti risponderemo.
Richiedi l'accesso ora