- Cache the access token in memory along with its
expires_invalue - Refresh proactively when within 60 seconds of expiry
- On
401, invalidate the cache and fetch a new token, then retry the request once
import { gzipSync } from 'zlib';
let _cachedToken = null;
let _tokenExpiresAt = 0;
async function getAccessToken() {
const now = Math.floor(Date.now() / 1000);
if (_cachedToken && now < _tokenExpiresAt - 60) return _cachedToken;
const res = await fetch(TOKEN_URL, {
method: "POST",
headers: {
"Authorization": "Basic " + Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64"),
"Content-Type": "application/x-www-form-urlencoded",
},
body: "grant_type=client_credentials&scope=billreview%3Awrite",
});
if (!res.ok) throw new Error(`Token request failed: ${res.status}`);
const data = await res.json();
_cachedToken = data.access_token;
_tokenExpiresAt = Math.floor(Date.now() / 1000) + (data.expires_in ?? 3600);
return _cachedToken;
}
async function reviewBill(billPayload) {
const compressed = gzipSync(Buffer.from(JSON.stringify(billPayload)));
const post = async (token) => fetch(`${API_ENDPOINT}/v1/review`, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"x-api-key": API_KEY,
"Content-Type": "application/json",
"Content-Encoding": "gzip",
},
body: compressed,
});
let res = await post(await getAccessToken());
if (res.status === 401) {
_cachedToken = null; // invalidate and retry once
res = await post(await getAccessToken());
}
if (!res.ok) throw new Error(`Review failed: ${res.status}`);
// Node.js fetch automatically decompresses the gzip response
return res.json();
}
import time, threading, requests, base64, gzip, json
_token_lock = threading.Lock()
_cached_token = None
_token_expires_at = 0.0
def get_access_token():
global _cached_token, _token_expires_at
with _token_lock:
if _cached_token and time.time() < _token_expires_at - 60:
return _cached_token
credentials = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()
res = requests.post(
TOKEN_URL,
headers={
"Authorization": f"Basic {credentials}",
"Content-Type": "application/x-www-form-urlencoded",
},
data="grant_type=client_credentials&scope=billreview%3Awrite",
)
res.raise_for_status()
data = res.json()
_cached_token = data["access_token"]
_token_expires_at = time.time() + data.get("expires_in", 3600)
return _cached_token
def review_bill(bill_payload):
global _cached_token
compressed = gzip.compress(json.dumps(bill_payload).encode())
def _post(token):
return requests.post(
f"{API_ENDPOINT}/v1/review",
headers={
"Authorization": f"Bearer {token}",
"x-api-key": API_KEY,
"Content-Type": "application/json",
"Content-Encoding": "gzip",
},
data=compressed,
)
res = _post(get_access_token())
if res.status_code == 401:
_cached_token = None // invalidate and retry once
res = _post(get_access_token())
res.raise_for_status()
return res.json() # requests automatically decompresses the gzip response
<?php
$cachedToken = null;
$tokenExpiresAt = 0;
function getAccessToken(): string {
global $cachedToken, $tokenExpiresAt;
if ($cachedToken && time() < $tokenExpiresAt - 60) {
return $cachedToken;
}
$credentials = base64_encode(CLIENT_ID . ':' . CLIENT_SECRET);
$ch = curl_init(TOKEN_URL);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Basic $credentials",
"Content-Type: application/x-www-form-urlencoded",
],
CURLOPT_POSTFIELDS => "grant_type=client_credentials&scope=billreview%3Awrite",
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) throw new RuntimeException("Token request failed: $status");
$data = json_decode($body, true);
$cachedToken = $data['access_token'];
$tokenExpiresAt = time() + ($data['expires_in'] ?? 3600);
return $cachedToken;
}
function reviewBill(array $billPayload): array {
global $cachedToken;
$compressed = gzencode(json_encode($billPayload));
$post = function (string $token) use ($compressed): array {
$ch = curl_init(API_ENDPOINT . '/v1/review');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => 'gzip', // auto-decompresses the gzip response
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $token",
"x-api-key: " . API_KEY,
"Content-Type: application/json",
"Content-Encoding: gzip",
],
CURLOPT_POSTFIELDS => $compressed,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [$status, json_decode($body, true)];
};
[$status, $result] = $post(getAccessToken());
if ($status === 401) {
$cachedToken = null; // invalidate and retry once
[$status, $result] = $post(getAccessToken());
}
if ($status !== 200) throw new RuntimeException("Review failed: $status");
return $result;
}
require 'net/http'
require 'uri'
require 'json'
require 'base64'
require 'zlib'
require 'stringio'
require 'monitor'
TOKEN_LOCK = Monitor.new
@cached_token = nil
@token_expires_at = 0.0
def get_access_token
TOKEN_LOCK.synchronize do
return @cached_token if @cached_token && Time.now.to_f < @token_expires_at - 60
uri = URI(TOKEN_URL)
req = Net::HTTP::Post.new(uri)
req['Authorization'] = "Basic #{Base64.strict_encode64("#{CLIENT_ID}:#{CLIENT_SECRET}")}"
req['Content-Type'] = 'application/x-www-form-urlencoded'
req.body = 'grant_type=client_credentials&scope=billreview%3Awrite'
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') { |h| h.request(req) }
raise "Token request failed: #{res.code}" unless res.is_a?(Net::HTTPSuccess)
data = JSON.parse(res.body)
@cached_token = data['access_token']
@token_expires_at = Time.now.to_f + (data['expires_in'] || 3600)
@cached_token
end
end
def post_review(compressed, token)
uri = URI("#{API_ENDPOINT}/v1/review")
req = Net::HTTP::Post.new(uri)
req['Authorization'] = "Bearer #{token}"
req['x-api-key'] = API_KEY
req['Content-Type'] = 'application/json'
req['Content-Encoding'] = 'gzip'
req['Accept-Encoding'] = 'gzip'
req.body = compressed
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') { |h| h.request(req) }
end
def review_bill(bill_payload)
compressed = Zlib.gzip(bill_payload.to_json)
res = post_review(compressed, get_access_token)
if res.code == '401'
@cached_token = nil # invalidate and retry once
res = post_review(compressed, get_access_token)
end
raise "Review failed: #{res.code}" unless res.is_a?(Net::HTTPSuccess)
# Decompress gzip response body if the server returned one
body = if res['content-encoding'] == 'gzip'
Zlib::GzipReader.new(StringIO.new(res.body)).read
else
res.body
end
JSON.parse(body)
end
using System.IO.Compression;
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
private static string? _cachedToken;
private static DateTimeOffset _tokenExpiresAt = DateTimeOffset.MinValue;
private static readonly SemaphoreSlim _tokenLock = new(1, 1);
private static async Task<string> GetAccessTokenAsync(HttpClient http)
{
await _tokenLock.WaitAsync();
try
{
if (_cachedToken != null && DateTimeOffset.UtcNow < _tokenExpiresAt.AddSeconds(-60))
return _cachedToken;
var credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{CLIENT_ID}:{CLIENT_SECRET}"));
var req = new HttpRequestMessage(HttpMethod.Post, TOKEN_URL);
req.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentials);
req.Content = new StringContent(
"grant_type=client_credentials&scope=billreview%3Awrite",
Encoding.UTF8, "application/x-www-form-urlencoded"
);
var res = await http.SendAsync(req);
res.EnsureSuccessStatusCode();
using var doc = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
_cachedToken = doc.RootElement.GetProperty("access_token").GetString()!;
var expiresIn = doc.RootElement.TryGetProperty("expires_in", out var ei) ? ei.GetInt32() : 3600;
_tokenExpiresAt = DateTimeOffset.UtcNow.AddSeconds(expiresIn);
return _cachedToken;
}
finally { _tokenLock.Release(); }
}
private static async Task<HttpResponseMessage> SendReviewRequestAsync(
HttpClient http, byte[] compressed, string token)
{
var req = new HttpRequestMessage(HttpMethod.Post, $"{API_ENDPOINT}/v1/review");
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
req.Headers.Add("x-api-key", API_KEY);
var body = new ByteArrayContent(compressed);
body.Headers.ContentType = new MediaTypeHeaderValue("application/json");
body.Headers.ContentEncoding.Add("gzip");
req.Content = body;
return await http.SendAsync(req);
}
private static async Task<JsonDocument> ReviewBillAsync(HttpClient http, object billPayload)
{
var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(billPayload));
using var ms = new MemoryStream();
using (var gz = new GZipStream(ms, CompressionLevel.Optimal, leaveOpen: true))
await gz.WriteAsync(jsonBytes);
var compressed = ms.ToArray();
var res = await SendReviewRequestAsync(http, compressed, await GetAccessTokenAsync(http));
if (res.StatusCode == HttpStatusCode.Unauthorized)
{
_cachedToken = null; // invalidate and retry once
res = await SendReviewRequestAsync(http, compressed, await GetAccessTokenAsync(http));
}
res.EnsureSuccessStatusCode();
// Manually decompress the gzip response body
var bytes = await res.Content.ReadAsByteArrayAsync();
using var ms = new MemoryStream(bytes);
using var gz = new GZipStream(ms, CompressionMode.Decompress);
return await JsonDocument.ParseAsync(gz);
}