Financial applications live and die by the quality, timeliness, and interpretability of their interest rate data. When you build pricing engines, risk analytics, ALM dashboards, or loan comparison tools, you need authoritative rates, sensible metadata, and robust time-series access. This post walks you through a complete, production-grade integration of the Polish Interbank Offered Rate 3-Month (WIBOR_3M) using the Interest Rates API from interestratesapi.com. We will go from discovery to live updates, compute statistics, shape OHLC bars for charts, compare impacts across benchmarks, and implement a minimal Node.js/Express caching service that can slot into your stack in minutes. Along the way, you will see clean, validated GET requests, error handling, and field-by-field explanations to help you ship faster and with higher confidence.
If you want to see the endpoints and experiment interactively as you read, here are quick links you can keep open in a separate tab: Try Interest Rates API, Explore Interest Rates API features, and Get started with Interest Rates API.
Why WIBOR 3M data matters for Finance applications
WIBOR 3M is a foundational reference rate for the Polish market. It influences floating-rate loans, derivatives pricing, corporate funding costs, and consumer lending benchmarks. For risk teams, it’s integral to stress testing and scenario design; for product teams, it helps derive APRs and forecast resets; for quants and economists, it’s a pillar in term structure modeling and macro analysis. Without a reliable API, practitioners are forced to wrangle disparate sources, manually normalize frequency and currencies, and write brittle parsers for irregular formats—time-consuming work that distracts from core product features and increases operational risk.
interestratesapi.com solves these pain points with:
- Clear, predictable REST endpoints using a single Base URL: https://interestratesapi.com/api/v1/
- GET-only semantics for simplicity and easier caching across client, CDN, and server layers
- Unified symbol identifiers across central bank, interbank, treasury, and reference rates
- Consistent response shapes for latest, historical, timeseries, OHLC, and fluctuation stats
- Practical conversion endpoint that helps you compare interest costs across benchmarks for loan scenarios
In practice, this means you can discover WIBOR_3M, pull the latest print, backfill time series to compute analytics, derive OHLC views for charting, quantify shifts over time windows, and even estimate loan interest cost differentials versus another benchmark, all with a few GET calls. The rest of this guide is a step-by-step implementation focused on WIBOR_3M so you can adapt it to your own stack quickly.
Core integration principles and architecture patterns
Before implementing, consider how interest rate data will flow through your system:
- Discovery and metadata: Use the symbols catalogue to confirm identifiers, categories, and frequencies. This prevents symbol drift and mismatches across environments.
- Hot path queries: Latest prints power dashboards and real-time decisions; design for fast GETs and simple cache invalidation.
- Backfills and rollups: Timeseries and OHLC endpoints support analytics windows (e.g., 3M, 6M, 1Y) and chart-ready transformations; run them via scheduled jobs or on-demand with caching.
- Point-in-time lookups: Historical requests underpin backtesting and consistency checks.
- Change statistics: Fluctuation provides min, max, and net deltas—excellent for quick summaries and alert thresholds.
- Comparative analysis: Convert supports loan interest cost comparisons, helpful for consumer tooling and treasury decisioning.
Operational best practices to adopt from day one:
- Always use GET with query parameters and include api_key as a query parameter in every request URL.
- Implement retries with exponential backoff on transient network faults.
- Cache successful responses with short TTLs at the edge where appropriate—especially for latest and metadata calls.
- Log response metadata (date ranges, frequencies, currencies) to validate assumptions and power monitoring.
- Normalize missing days in time series appropriately for your model (e.g., forward fill when appropriate for display, never for PnL).
Next, let’s integrate WIBOR_3M with a clean, logical flow: discover symbol → latest → timeseries → historical → fluctuation → ohlc → convert. Each step includes cURL, Python, JavaScript, and PHP, plus explanations and realistic JSON responses so you can copy/paste into your environment.
1) Discover WIBOR_3M via the Symbols catalogue (/symbols)
The symbols endpoint helps you enumerate and filter available rate identifiers. This is crucial for configuration UIs and CI/CD validations. Since we’re integrating the Polish Interbank Offered Rate 3-Month, we can filter by category=interbank and base=PLN to locate WIBOR_3M explicitly.
Endpoint purpose
GET /api/v1/symbols returns a structured list of available symbols and their metadata. It’s useful for:
- Pre-deployment checks: Ensure WIBOR_3M exists and confirm frequency.
- Dynamic UI: Populate dropdowns for category and currency.
- Automation: Validate service contracts when adding new rate families.
Example requests
cURL
curl "https://interestratesapi.com/api/v1/symbols?category=interbank&base=PLN&api_key=YOUR_KEY"
Python (requests)
import requests
resp = requests.get(
"https://interestratesapi.com/api/v1/symbols",
params=dict(category="interbank", base="PLN", api_key="YOUR_KEY")
)
data = resp.json()
print(data)
JavaScript (fetch)
const response = await fetch(
"https://interestratesapi.com/api/v1/symbols?category=interbank&base=PLN&api_key=YOUR_KEY"
);
const data = await response.json();
console.log(data);
PHP
<?php
$baseUrl = "https://interestratesapi.com/api/v1/symbols?category=interbank&base=PLN&api_key=YOUR_KEY";
$ch = curl_init($baseUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
echo $result;
?
Sample JSON and field meanings
{
"success": true,
"count": 1,
"symbols": [
{
"symbol": "WIBOR_3M",
"name": "Polish Interbank Offered Rate - 3 Month",
"category": "interbank",
"country_code": "PL",
"currency_code": "PLN",
"frequency": "daily",
"description": "3-month Polish zloty interbank offered rate"
}
]
}
- success: boolean indicating if the request was successful.
- count: number of symbols returned after filters.
- symbols: list of entries, each with symbol (the identifier you will use), name, category, country_code, currency_code, frequency, and description.
Implementation note: Store symbol metadata locally to enable human-readable labels, and validate frequency before computing aggregations. For WIBOR_3M, frequency is often daily; always confirm with the response.
2) Fetch the latest WIBOR 3M print (/latest)
Dashboards, pricing widgets, and real-time monitors typically need the current rate. The /latest endpoint returns the most recent published values per symbol. You can query multiple symbols at once; we will focus on WIBOR_3M and, optionally, add EURIBOR_3M or US_TREASURY_3M for comparisons.
Endpoint purpose
GET /api/v1/latest provides the latest value and associated metadata like date and currency. It’s best for:
- Front-end display of the current benchmark for end users.
- Triggering alerts when fresh updates arrive.
- Baseline for comparative analytics (e.g., spreads vs. other rates).
Example requests
cURL
curl "https://interestratesapi.com/api/v1/latest?symbols=WIBOR_3M&api_key=YOUR_KEY"
Python (requests)
import requests
resp = requests.get(
"https://interestratesapi.com/api/v1/latest",
params=dict(symbols="WIBOR_3M", api_key="YOUR_KEY")
)
data = resp.json()
print(data)
JavaScript (fetch)
const response = await fetch(
"https://interestratesapi.com/api/v1/latest?symbols=WIBOR_3M&api_key=YOUR_KEY"
);
const data = await response.json();
console.log(data);
PHP
<?php
$url = "https://interestratesapi.com/api/v1/latest?symbols=WIBOR_3M&api_key=YOUR_KEY";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
echo $result;
?
Sample JSON and field meanings
{
"success": true,
"date": "2026-09-16",
"base": "PLN",
"rates": {
"WIBOR_3M": 5.95
},
"dates": {
"WIBOR_3M": "2026-09-16"
},
"currencies": {
"WIBOR_3M": "PLN"
}
}
- date: the response-level date indicating when the snapshot is considered current.
- rates: symbol-to-value mapping; you’ll parse WIBOR_3M here.
- dates: per-symbol last update date—useful if you query multiple symbols with varying recency.
- currencies: per-symbol currency code; confirm PLN for WIBOR_3M to avoid cross-currency confusion.
Production tip: For multi-symbol calls, always check dates[symbol] and currencies[symbol] to avoid combining mismatched vintages or currencies in analytics.
3) Pull historical time series for analytics (/timeseries)
To compute moving averages, volatility bands, curve fitting, or display charts, you need historical data. The /timeseries endpoint returns daily values between start and end dates for one or more symbols. For WIBOR_3M, this is the backbone of quant and BI workflows.
Endpoint purpose
GET /api/v1/timeseries returns time-indexed values for requested symbols across a date range. Use this to:
- Power charts and advanced analytics in your app.
- Compute drawdowns, rolling averages, and other indicators.
- Backtest trading or hedging logic dependent on rate paths.
Example requests
cURL
curl "https://interestratesapi.com/api/v1/timeseries?start=2025-09-16&end=2026-09-16&symbols=WIBOR_3M&api_key=YOUR_KEY"
Python (requests)
import requests
from statistics import mean
params = dict(
start="2025-09-16",
end="2026-09-16",
symbols="WIBOR_3M",
api_key="YOUR_KEY"
)
resp = requests.get("https://interestratesapi.com/api/v1/timeseries", params=params)
ts = resp.json()
series = ts["rates"]["WIBOR_3M"]
# compute a simple average
avg = mean(series.values())
print("Average WIBOR_3M over period:", round(avg, 4))
JavaScript (fetch)
const url = "https://interestratesapi.com/api/v1/timeseries?start=2025-09-16&end=2026-09-16&symbols=WIBOR_3M&api_key=YOUR_KEY";
const response = await fetch(url);
const data = await response.json();
const series = data.rates.WIBOR_3M;
const values = Object.values(series);
const avg = values.reduce((a, b) => a + b, 0) / values.length;
console.log("Average WIBOR_3M:", Number(avg.toFixed(4)));
PHP
<?php
$qs = http_build_query([
"start" => "2025-09-16",
"end" => "2026-09-16",
"symbols" => "WIBOR_3M",
"api_key" => "YOUR_KEY"
]);
$url = "https://interestratesapi.com/api/v1/timeseries?$qs";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
$data = json_decode($result, true);
$series = $data["rates"]["WIBOR_3M"];
$sum = 0.0; $n = 0;
foreach ($series as $d => $v) { $sum += $v; $n++; }
$avg = $n > 0 ? $sum / $n : null;
echo "Average WIBOR_3M: " . round($avg, 4) . PHP_EOL;
?
Sample JSON and field meanings
{
"success": true,
"base": "PLN",
"start_date": "2025-09-16",
"end_date": "2026-09-16",
"rates": {
"WIBOR_3M": {
"2025-09-16": 6.10,
"2025-09-17": 6.10,
"2025-09-18": 6.08,
"2025-09-19": 6.07,
"2025-09-22": 6.07,
"2025-09-23": 6.05
// ... daily observations through end_date
}
},
"frequencies": { "WIBOR_3M": "daily" },
"currencies": { "WIBOR_3M": "PLN" }
}
- rates.WIBOR_3M: date-indexed map of daily observations, suitable for charts and computation.
- frequencies: confirms the cadence of each symbol; do not assume daily without checking.
- currencies: validates the currency dimension of the time series.
Practical use: Combine this payload with your charting library. If you need aggregated periods (weekly, monthly), you can also use /ohlc for candlestick-style summaries.
4) Retrieve a point-in-time value (/historical)
Point-in-time values are essential for audits, loan reset calculations on a given date, and historical reproduction of analytics. The /historical endpoint returns the specific date’s value. For symbols with lower frequency, the service uses the last available observation within that month, but WIBOR_3M is typically daily.
Endpoint purpose
GET /api/v1/historical returns the value of a symbol on a specific date. Use cases include:
- Loan reset logic for floating-rate products that require the official rate as of a contractual reset date.
- Backtesting strategies that rely on end-of-day or month-end snapshots.
- Reproducibility: retrieving exactly what your risk engine saw on a prior date.
Example requests
cURL
curl "https://interestratesapi.com/api/v1/historical?date=2025-06-15&symbols=WIBOR_3M&api_key=YOUR_KEY"
Python (requests)
import requests
resp = requests.get(
"https://interestratesapi.com/api/v1/historical",
params=dict(date="2025-06-15", symbols="WIBOR_3M", api_key="YOUR_KEY")
)
data = resp.json()
print(data)
JavaScript (fetch)
const response = await fetch(
"https://interestratesapi.com/api/v1/historical?date=2025-06-15&symbols=WIBOR_3M&api_key=YOUR_KEY"
);
const data = await response.json();
console.log(data);
PHP
<?php
$url = "https://interestratesapi.com/api/v1/historical?date=2025-06-15&symbols=WIBOR_3M&api_key=YOUR_KEY";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
echo $result;
?
Sample JSON and field meanings
{
"success": true,
"date": "2025-06-15",
"base": "PLN",
"rates": { "WIBOR_3M": 6.22 },
"currencies": { "WIBOR_3M": "PLN" }
}
- date: the date requested; useful for ensuring you retrieved the precise period you intended.
- rates.WIBOR_3M: the WIBOR 3M value on that date.
- currencies.WIBOR_3M: the currency code, which should be PLN.
Operational tip: If you drive monthly resets off a specific business day, align your logic with market calendars when selecting the date parameter, then rely on this endpoint for reproducible retrieval.
5) Quantify changes over time (/fluctuation)
Executive summaries and alert systems often require quick answers: how much did WIBOR_3M move this quarter? What was the range? The /fluctuation endpoint gives you a compact set of change statistics: start value, end value, net change, percent change, and the observed high/low.
Endpoint purpose
GET /api/v1/fluctuation computes change statistics over a period. Typical uses:
- Portfolio commentary: summarize rate drift over reporting windows.
- UI badges: “WIBOR_3M -0.12 (-1.8%) since last month.”
- Trigger thresholds: act when end_value breaches bounds or when change_pct crosses a tolerance.
Example requests
cURL
curl "https://interestratesapi.com/api/v1/fluctuation?start=2025-09-16&end=2026-09-16&symbols=WIBOR_3M&api_key=YOUR_KEY"
Python (requests)
import requests
resp = requests.get(
"https://interestratesapi.com/api/v1/fluctuation",
params=dict(start="2025-09-16", end="2026-09-16", symbols="WIBOR_3M", api_key="YOUR_KEY")
)
data = resp.json()
stats = data["rates"]["WIBOR_3M"]
print("Change:", stats["change"], "Change %:", stats["change_pct"], "High:", stats["high"], "Low:", stats["low"])
JavaScript (fetch)
const url = "https://interestratesapi.com/api/v1/fluctuation?start=2025-09-16&end=2026-09-16&symbols=WIBOR_3M&api_key=YOUR_KEY";
const response = await fetch(url);
const data = await response.json();
const r = data.rates.WIBOR_3M;
console.log(`Start ${r.start_value} End ${r.end_value} Δ ${r.change} (${r.change_pct}%) High ${r.high} Low ${r.low}`);
PHP
<?php
$params = http_build_query([
"start" => "2025-09-16",
"end" => "2026-09-16",
"symbols" => "WIBOR_3M",
"api_key" => "YOUR_KEY"
]);
$url = "https://interestratesapi.com/api/v1/fluctuation?$params";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
echo $result;
?
Sample JSON and field meanings
{
"success": true,
"rates": {
"WIBOR_3M": {
"start_date": "2025-09-16",
"end_date": "2026-09-16",
"start_value": 6.10,
"end_value": 5.95,
"change": -0.15,
"change_pct": -2.46,
"high": 6.20,
"low": 5.90
}
}
}
- start_value, end_value: bookending values for your period-based comparisons.
- change, change_pct: absolute and percentage difference—ready for reporting.
- high, low: observed extremes in the requested date window.
Design tip: Use change_pct with rounding appropriate for your audience (e.g., two decimals for business users, full precision for quants).
6) Generate OHLC candlesticks for charts (/ohlc)
Candlestick charts are a visual staple for time series. The /ohlc endpoint computes open-high-low-close bars from daily data for a chosen periodization (weekly, monthly, or quarterly). For WIBOR_3M, OHLC can provide intuitive overviews of rate behavior each month.
Endpoint purpose
GET /api/v1/ohlc aggregates daily observations into OHLC bars. Use this to:
- Render financial charts that highlight ranges and closes.
- Support monthly or quarterly reporting visuals without writing your own aggregator.
- Compress large daily datasets into digestible summaries.
Example requests
cURL
curl "https://interestratesapi.com/api/v1/ohlc?symbols=WIBOR_3M&period=monthly&start=2025-01-01&end=2026-09-16&api_key=YOUR_KEY"
Python (requests)
import requests
params = dict(
symbols="WIBOR_3M",
period="monthly",
start="2025-01-01",
end="2026-09-16",
api_key="YOUR_KEY"
)
resp = requests.get("https://interestratesapi.com/api/v1/ohlc", params=params)
ohlc = resp.json()
print(ohlc)
JavaScript (fetch)
const response = await fetch(
"https://interestratesapi.com/api/v1/ohlc?symbols=WIBOR_3M&period=monthly&start=2025-01-01&end=2026-09-16&api_key=YOUR_KEY"
);
const data = await response.json();
console.log(data);
PHP
<?php
$query = http_build_query([
"symbols" => "WIBOR_3M",
"period" => "monthly",
"start" => "2025-01-01",
"end" => "2026-09-16",
"api_key" => "YOUR_KEY"
]);
$url = "https://interestratesapi.com/api/v1/ohlc?$query";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
echo $result;
?
Sample JSON and field meanings
{
"success": true,
"period": "monthly",
"start_date": "2025-01-01",
"end_date": "2026-09-16",
"rates": {
"WIBOR_3M": [
{
"period": "2025-01",
"open": 6.40,
"high": 6.42,
"low": 6.35,
"close": 6.36,
"data_points": 22
},
{
"period": "2025-02",
"open": 6.36,
"high": 6.38,
"low": 6.30,
"close": 6.31,
"data_points": 20
}
// ... months through end_date
]
}
}
- period: your chosen aggregation level (monthly shown above).
- open, high, low, close: computed from daily data per period.
- data_points: how many daily observations were used to compute the bar.
Engineering note: Because OHLC is computed on-the-fly from daily observations, you avoid maintaining your own rollup pipelines. This is particularly useful for teams delivering chart-heavy UIs who want server-side aggregation without extra ETL code.
7) Compare loan interest costs across benchmarks (/convert)
For consumer finance apps, SME tools, or treasury comparisons, it’s often helpful to estimate interest costs under two different benchmark rates. The /convert endpoint provides a straightforward comparison of simple loan interest costs at the latest rate for two symbols. You can use WIBOR_3M versus EURIBOR_3M to illustrate cross-border funding impacts, or compare WIBOR_3M against a reference like PRIME_RATE.
Endpoint purpose
GET /api/v1/convert computes total interest and payment projections given an amount, term, and two benchmark symbols’ latest rates. Use cases:
- Loan comparison widgets that show clients indicative costs under different benchmarks.
- Internal treasury analysis to weigh alternative funding sources.
- Education tools explaining the impact of rate spreads on total payments.
Example requests
cURL
curl "https://interestratesapi.com/api/v1/convert?from=WIBOR_3M&to=EURIBOR_3M&amount=250000&term_months=24&api_key=YOUR_KEY"
Python (requests)
import requests
params = dict(
from="WIBOR_3M",
to="EURIBOR_3M",
amount=250000,
term_months=24,
api_key="YOUR_KEY"
)
# 'from' is a reserved keyword in Python; pass via dict and use requests.get
resp = requests.get("https://interestratesapi.com/api/v1/convert", params=params)
data = resp.json()
print(data)
JavaScript (fetch)
const response = await fetch(
"https://interestratesapi.com/api/v1/convert?from=WIBOR_3M&to=EURIBOR_3M&amount=250000&term_months=24&api_key=YOUR_KEY"
);
const data = await response.json();
console.log(data);
PHP
<?php
$q = http_build_query([
"from" => "WIBOR_3M",
"to" => "EURIBOR_3M",
"amount" => 250000,
"term_months" => 24,
"api_key" => "YOUR_KEY"
]);
$url = "https://interestratesapi.com/api/v1/convert?$q";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
echo $result;
?
Sample JSON and field meanings
{
"success": true,
"amount": 250000,
"term_months": 24,
"from": {
"symbol": "WIBOR_3M",
"rate": 5.95,
"date": "2026-09-16",
"total_interest": 29750.00,
"total_payment": 279750.00
},
"to": {
"symbol": "EURIBOR_3M",
"rate": 3.85,
"date": "2026-09-16",
"total_interest": 19250.00,
"total_payment": 269250.00
},
"difference": {
"rate_spread": 2.10,
"interest_saved": 10500.00
}
}
- from, to: nested objects showing each symbol’s latest rate, date, and computed totals.
- difference: rate_spread (from minus to) and interest_saved (if ‘to’ is cheaper).
Note: The endpoint estimates simple interest cost comparisons for clarity; adapt your own amortization logic for production lending if you require compounding, payment schedules, or more nuanced interest accrual conventions.
Error handling and robust client design
Even well-formed integrations encounter edge cases. Your client should handle common errors from interestratesapi.com gracefully. All endpoints return a consistent error shape with success=false and a descriptive error message; some 404 responses include a details field with helpful date range information.
Common error responses
- 401 Unauthorized: Missing api_key, invalid or revoked key, user not found.
- 403 Forbidden: Account without active plan.
- 404 Not Found: No symbols matched or no data for the requested date/range; may include details about available ranges.
- 422 Unprocessable Entity: Validation error—wrong date format (must be Y-m-d), invalid symbol, or malformed parameters.
- 429 Too Many Requests: Request quota exhausted. You can inspect Retry-After and rate limit headers to pace subsequent calls.
Error JSON example
{
"success": false,
"error": "No data for requested date range",
"details": "Available range for WIBOR_3M starts at 2000-01-03"
}
Implementation strategies:
- Input validation: Enforce Y-m-d date format client-side and server-side to avoid 422s.
- Symbol validation: Use /symbols upfront; reject unknown identifiers before calling other endpoints.
- Retry/backoff: On transient network errors or 5xx, apply exponential backoff with jitter. Respect Retry-After for 429 responses.
- Graceful degradation: Return cached or last-known-good values when live requests fail, with clear freshness indicators.
Rate limit headers and client pacing
When you receive a 429, the response includes headers to guide pacing:
- X-RateLimit-Limit: The maximum number of requests permitted in the current window.
- X-RateLimit-Remaining: How many requests are left in the current window.
- X-RateLimit-Reset: Unix timestamp or epoch-like value indicating when the limit resets.
- Retry-After: The number of seconds to wait before retrying the same request.
Client guidance:
- Honor Retry-After exactly before issuing the next call to the same endpoint with the same parameters.
- Throttle proactively when X-RateLimit-Remaining approaches zero—queue non-urgent calls and prioritize mission-critical paths.
- Centralize your HTTP client to uniformly handle response headers and implement scheduling logic.
Mini end-to-end Node.js/Express endpoint with in-memory caching
Below is a compact Express service that serves WIBOR_3M data to downstream clients. It caches the /latest response for a short TTL, validates input, surfaces minimal diagnostics, and demonstrates proper GET usage with the api_key query parameter. You can extend this with Redis or a CDN for production-grade caching and plug it into your front-end with a single fetch call.
// package.json dependencies:
// "express": "^4.18.2", "node-fetch": "^3.3.2"
import express from "express";
import fetch from "node-fetch";
const app = express();
const PORT = process.env.PORT || 8080;
const API_BASE = "https://interestratesapi.com/api/v1";
const API_KEY = process.env.RATES_API_KEY; // injected at runtime
// Simple in-memory cache
let cache = {
latest: { data: null, ts: 0, ttlMs: 30_000 } // 30 seconds TTL
};
function isFresh(entry) {
return entry.data && (Date.now() - entry.ts) < entry.ttlMs;
}
// GET /wibor/latest returns latest WIBOR_3M with cache
app.get("/wibor/latest", async (req, res) => {
try {
if (isFresh(cache.latest)) {
return res.json({ source: "cache", ...cache.latest.data });
}
const url = `${API_BASE}/latest?symbols=WIBOR_3M&api_key=${encodeURIComponent(API_KEY)}`;
const r = await fetch(url);
if (!r.ok) {
const text = await r.text();
return res.status(r.status).send(text);
}
const json = await r.json();
cache.latest = { data: json, ts: Date.now(), ttlMs: cache.latest.ttlMs };
res.json({ source: "live", ...json });
} catch (err) {
res.status(500).json({ success: false, error: "Upstream error", detail: String(err) });
}
});
// GET /wibor/timeseries?start=YYYY-MM-DD&end=YYYY-MM-DD
app.get("/wibor/timeseries", async (req, res) => {
const { start, end } = req.query;
if (!start || !end) {
return res.status(422).json({ success: false, error: "Missing required start and end params (Y-m-d)" });
}
try {
const url = `${API_BASE}/timeseries?start=${encodeURIComponent(start)}&end=${encodeURIComponent(end)}&symbols=WIBOR_3M&api_key=${encodeURIComponent(API_KEY)}`;
const r = await fetch(url);
if (!r.ok) {
const payload = await r.text();
return res.status(r.status).send(payload);
}
const json = await r.json();
res.json(json);
} catch (err) {
res.status(500).json({ success: false, error: "Upstream error", detail: String(err) });
}
});
// Basic health check
app.get("/healthz", (req, res) => res.send("ok"));
app.listen(PORT, () => {
console.log(`Server running on :${PORT}`);
});
Enhancements to consider:
- Add Redis for shared cache across instances and warmup on cold start.
- Use circuit breakers to fail fast when upstream is slow, and serve cached values for resiliency.
- Surface observability: log symbols, response times, cache hit ratio, and error rates.
- Implement retry/backoff on transient upstream failures and respect Retry-After on 429 responses.
End-to-end JSON example walkthrough
Let’s assemble a short workflow to demonstrate how responses fit together. Suppose your dashboard needs:
- Latest WIBOR_3M for headline display.
- Last 6 months of daily data for charts.
- Monthly OHLC summaries for a compact sparkline view.
- Change stats for a year-to-date summary.
You might call the following endpoints:
- /latest?symbols=WIBOR_3M
- /timeseries?start=2026-03-16&end=2026-09-16&symbols=WIBOR_3M
- /ohlc?symbols=WIBOR_3M&period=monthly&start=2026-01-01&end=2026-09-16
- /fluctuation?start=2026-01-01&end=2026-09-16&symbols=WIBOR_3M
A representative combined response set could look like this:
{
"latest": {
"success": true,
"date": "2026-09-16",
"base": "PLN",
"rates": { "WIBOR_3M": 5.95 },
"dates": { "WIBOR_3M": "2026-09-16" },
"currencies": { "WIBOR_3M": "PLN" }
},
"timeseries": {
"success": true,
"base": "PLN",
"start_date": "2026-03-16",
"end_date": "2026-09-16",
"rates": {
"WIBOR_3M": {
"2026-03-16": 6.05,
"2026-03-17": 6.05,
"2026-03-18": 6.04
// ... through 2026-09-16
}
},
"frequencies": { "WIBOR_3M": "daily" },
"currencies": { "WIBOR_3M": "PLN" }
},
"ohlc": {
"success": true,
"period": "monthly",
"start_date": "2026-01-01",
"end_date": "2026-09-16",
"rates": {
"WIBOR_3M": [
{ "period": "2026-01", "open": 6.20, "high": 6.22, "low": 6.18, "close": 6.19, "data_points": 22 },
{ "period": "2026-02", "open": 6.19, "high": 6.20, "low": 6.15, "close": 6.16, "data_points": 20 }
// ...
]
}
},
"fluctuation": {
"success": true,
"rates": {
"WIBOR_3M": {
"start_date": "2026-01-01",
"end_date": "2026-09-16",
"start_value": 6.19,
"end_value": 5.95,
"change": -0.24,
"change_pct": -3.88,
"high": 6.22,
"low": 5.92
}
}
}
}
From this data:
- Front-end: display 5.95% as the latest WIBOR_3M with the date 2026-09-16.
- Charts: feed the daily series directly into your line graph, and overlay OHLC for a monthly view.
- Summary: show that the rate fell 24 bps YTD with a peak of 6.22 and a trough of 5.92.
Complete endpoint-by-endpoint code index (WIBOR_3M)
/symbols
cURL
curl "https://interestratesapi.com/api/v1/symbols?category=interbank&base=PLN&api_key=YOUR_KEY"
Python
import requests
r = requests.get(
"https://interestratesapi.com/api/v1/symbols",
params=dict(category="interbank", base="PLN", api_key="YOUR_KEY")
)
print(r.json())
JavaScript
const res = await fetch("https://interestratesapi.com/api/v1/symbols?category=interbank&base=PLN&api_key=YOUR_KEY");
console.log(await res.json());
PHP
<?php
$u = "https://interestratesapi.com/api/v1/symbols?category=interbank&base=PLN&api_key=YOUR_KEY";
$ch = curl_init($u); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($ch); curl_close($ch);
?
/latest
cURL
curl "https://interestratesapi.com/api/v1/latest?symbols=WIBOR_3M&api_key=YOUR_KEY"
Python
import requests
r = requests.get(
"https://interestratesapi.com/api/v1/latest",
params=dict(symbols="WIBOR_3M", api_key="YOUR_KEY")
)
print(r.json())
JavaScript
const r = await fetch("https://interestratesapi.com/api/v1/latest?symbols=WIBOR_3M&api_key=YOUR_KEY");
console.log(await r.json());
PHP
<?php
$u = "https://interestratesapi.com/api/v1/latest?symbols=WIBOR_3M&api_key=YOUR_KEY";
$ch = curl_init($u); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($ch); curl_close($ch);
?
/timeseries
cURL
curl "https://interestratesapi.com/api/v1/timeseries?start=2025-09-16&end=2026-09-16&symbols=WIBOR_3M&api_key=YOUR_KEY"
Python
import requests
r = requests.get(
"https://interestratesapi.com/api/v1/timeseries",
params=dict(start="2025-09-16", end="2026-09-16", symbols="WIBOR_3M", api_key="YOUR_KEY")
)
print(r.json())
JavaScript
const r = await fetch("https://interestratesapi.com/api/v1/timeseries?start=2025-09-16&end=2026-09-16&symbols=WIBOR_3M&api_key=YOUR_KEY");
console.log(await r.json());
PHP
<?php
$q = http_build_query([
"start" => "2025-09-16",
"end" => "2026-09-16",
"symbols" => "WIBOR_3M",
"api_key" => "YOUR_KEY"
]);
$u = "https://interestratesapi.com/api/v1/timeseries?$q";
$ch = curl_init($u); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($ch); curl_close($ch);
?
/historical
cURL
curl "https://interestratesapi.com/api/v1/historical?date=2025-06-15&symbols=WIBOR_3M&api_key=YOUR_KEY"
Python
import requests
r = requests.get(
"https://interestratesapi.com/api/v1/historical",
params=dict(date="2025-06-15", symbols="WIBOR_3M", api_key="YOUR_KEY")
)
print(r.json())
JavaScript
const r = await fetch("https://interestratesapi.com/api/v1/historical?date=2025-06-15&symbols=WIBOR_3M&api_key=YOUR_KEY");
console.log(await r.json());
PHP
<?php
$u = "https://interestratesapi.com/api/v1/historical?date=2025-06-15&symbols=WIBOR_3M&api_key=YOUR_KEY";
$ch = curl_init($u); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($ch); curl_close($ch);
?
/fluctuation
cURL
curl "https://interestratesapi.com/api/v1/fluctuation?start=2025-09-16&end=2026-09-16&symbols=WIBOR_3M&api_key=YOUR_KEY"
Python
import requests
r = requests.get(
"https://interestratesapi.com/api/v1/fluctuation",
params=dict(start="2025-09-16", end="2026-09-16", symbols="WIBOR_3M", api_key="YOUR_KEY")
)
print(r.json())
JavaScript
const r = await fetch("https://interestratesapi.com/api/v1/fluctuation?start=2025-09-16&end=2026-09-16&symbols=WIBOR_3M&api_key=YOUR_KEY");
console.log(await r.json());
PHP
<?php
$q = http_build_query([
"start" => "2025-09-16",
"end" => "2026-09-16",
"symbols" => "WIBOR_3M",
"api_key" => "YOUR_KEY"
]);
$u = "https://interestratesapi.com/api/v1/fluctuation?$q";
$ch = curl_init($u); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($ch); curl_close($ch);
?
/ohlc
cURL
curl "https://interestratesapi.com/api/v1/ohlc?symbols=WIBOR_3M&period=monthly&start=2025-01-01&end=2026-09-16&api_key=YOUR_KEY"
Python
import requests
r = requests.get(
"https://interestratesapi.com/api/v1/ohlc",
params=dict(symbols="WIBOR_3M", period="monthly", start="2025-01-01", end="2026-09-16", api_key="YOUR_KEY")
)
print(r.json())
JavaScript
const r = await fetch("https://interestratesapi.com/api/v1/ohlc?symbols=WIBOR_3M&period=monthly&start=2025-01-01&end=2026-09-16&api_key=YOUR_KEY");
console.log(await r.json());
PHP
<?php
$q = http_build_query([
"symbols" => "WIBOR_3M",
"period" => "monthly",
"start" => "2025-01-01",
"end" => "2026-09-16",
"api_key" => "YOUR_KEY"
]);
$u = "https://interestratesapi.com/api/v1/ohlc?$q";
$ch = curl_init($u); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($ch); curl_close($ch);
?
/convert
cURL
curl "https://interestratesapi.com/api/v1/convert?from=WIBOR_3M&to=EURIBOR_3M&amount=100000&term_months=12&api_key=YOUR_KEY"
Python
import requests
params = dict(from="WIBOR_3M", to="EURIBOR_3M", amount=100000, term_months=12, api_key="YOUR_KEY")
r = requests.get("https://interestratesapi.com/api/v1/convert", params=params)
print(r.json())
JavaScript
const r = await fetch("https://interestratesapi.com/api/v1/convert?from=WIBOR_3M&to=EURIBOR_3M&amount=100000&term_months=12&api_key=YOUR_KEY");
console.log(await r.json());
PHP
<?php
$q = http_build_query([
"from" => "WIBOR_3M",
"to" => "EURIBOR_3M",
"amount" => 100000,
"term_months" => 12,
"api_key" => "YOUR_KEY"
]);
$u = "https://interestratesapi.com/api/v1/convert?$q";
$ch = curl_init($u); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($ch); curl_close($ch);
?
Field-by-field guidance: interpreting responses in practice
Across endpoints, the API provides consistent keys:
- success: Always check before parsing other fields.
- date/start_date/end_date: Validate these against your UI and analytics windows; misaligned dates can cause subtle bugs.
- rates: A structured map containing either flat symbol values (latest/historical) or time-indexed maps (timeseries) or nested stats (fluctuation).
- frequencies/currencies: Confirm frequency for transforming data (e.g., sampling) and validate currency alignment before mixing symbols.
- period/ohlc keys: Use for visual charts; high/low guardrails are excellent for highlighting winners/losers on dashboards.
- difference in /convert: Communicate spread-driven business impacts simply and clearly to non-technical stakeholders.
Conversion to internal models: Normalizing these responses into your application’s data structures helps ensure type safety and clarity. Consider defining domain objects like RateObservation { date, symbol, value, currency } and OhlcBar { period, open, high, low, close, points } to enforce schema consistency end-to-end.
Performance and reliability patterns for finance-grade APIs
Finance workloads benefit from robust, predictable behaviors:
- Routing and retries: Centralize HTTP clients to implement retries with backoff for transient upstream issues.
- Caching and staleness: Cache /latest briefly to shield UIs from bursts and use well-labeled staleness windows so stakeholders know recency at a glance.
- Fallback chains: When rendering composite dashboards, degrade gracefully if a non-critical panel fails; show cached OHLC while timeseries loads.
- Health checks: Periodically call lightweight endpoints and maintain dashboards of status signals and response times to detect anomalies early.
- Audit trails: Log query parameters and response dates for reproducibility; in regulated contexts, this is essential for traceability.
Observability best practices:
- Log request URLs (excluding sensitive keys), response status codes, and response sizes.
- Track percentile latencies per endpoint; high p95 suggests caching or batching opportunities.
- Alert on increases in 4xx/5xx rates; investigate 422 spikes for client-side validation gaps.
Real-world scenarios for WIBOR 3M in fintech products
Your application can use WIBOR_3M in many scenarios:
- Loan repricing engine: On reset dates, retrieve /historical for exact values. Combine with your margin rules to compute new coupon rates. Store both observed benchmark and resulting coupon for audit.
- Risk and ALM: Use /timeseries to compute interest rate sensitivity of deposits and loans. Pull /fluctuation for board-level summaries on quarterly movements.
- Consumer transparency: Use /convert to show how a loan priced off WIBOR_3M compares to EURIBOR_3M or PRIME_RATE in total interest terms over 12–24 months.
- Investor communications: Visualize monthly OHLC bars to depict distribution of rates and contextualize statements like “tight trading range in Q2.”
- Macro analytics: Combine WIBOR_3M with US_TREASURY_3M or EURIBOR_3M to analyze cross-market stress and monetary policy transmission effects.
Troubleshooting checklist
- Validation errors (422): Check date format (Y-m-d), symbol spelling (WIBOR_3M), and ensure all query parameters are strings or numbers.
- No data (404): Narrow your date range based on details returned; call /symbols or attempt a broader /timeseries window to discover coverage.
- Unexpected NaNs in charts: Ensure you treat non-trading or missing days consistently; do not force interpolate where it breaks financial logic.
- Mixed currencies: When comparing symbols, always confirm currencies[symbol]; do not inadvertently mix PLN and EUR in a single derived metric unless intended.
- Slow dashboards: Cache /latest briefly, prefetch OHLC for common windows, and lazy-load deep history behind user interactions.
Security, governance, and deployment considerations
For production deployments:
- Per-app credentials: Isolate keys per microservice or environment so you can rotate or audit with precision.
- Roles and auditing: Record which service accessed which symbol and when; maintain immutable logs for compliance.
- Regional hosting: Place your services and caches close to users for lower latency; co-locate with your API-consuming services to minimize tail latencies.
- Configuration management: Keep the base URL and symbols list in configuration, not hardcoded, to enable safe updates.
- Disaster readiness: Implement circuit breakers and fallbacks as described above; ensure your SRE playbooks include upstream failure modes.
Putting it all together: a concise integration plan
- Discover and verify symbol: Call /symbols with category=interbank and base=PLN to confirm WIBOR_3M.
- Implement hot path for /latest: Cache lightly; validate date and currency fields per symbol.
- Backfill analytics with /timeseries: Store raw time series; use in charts, risk, and analytics engines.
- Support point-in-time with /historical: Drive loan resets, audits, and reproducible backtests.
- Build summaries with /fluctuation: Show change stats and highlight extremes in dashboards.
- Render compressed visuals with /ohlc: Efficient monthly or quarterly bars without extra ETL.
- Enable comparisons with /convert: Add loan cost calculators and treasury decision aids.
- Harden with error handling: Validate inputs, catch 4xx/5xx, apply retries, and respect Retry-After guidance.
- Observe and optimize: Log timings, cache hot endpoints, and monitor trends.
Conclusion and next steps
WIBOR_3M is a cornerstone rate for Poland’s financial ecosystem. With interestratesapi.com, you can integrate it cleanly across discovery, latest snapshots, time series analytics, OHLC charting, change statistics, and cross-benchmark comparisons—all via straightforward GET requests with the api_key in the query string. This guide provided production-ready cURL, Python, JavaScript, and PHP examples for every endpoint, an Express caching service you can deploy today, and extensive field-by-field explanations to minimize integration risk and speed up delivery.
From here, you can expand your coverage to complementary interbank benchmarks (EURIBOR_3M, STIBOR_3M), central bank rates for policy context, and treasury curves for multi-market analysis. For more information, live tests, and feature exploration, visit these resources:
Build with confidence: keep requests idempotent, observe clear error pathways, standardize schemas across services, and deploy with caching and resilience patterns. With this foundation, your finance application can deliver accurate, timely, and insightful WIBOR 3M analytics at scale.




