Fintech applications that price loans, manage treasury risk, and analyze monetary conditions all depend on accurate, timely interbank reference rates. For Indonesia’s money markets, the JIBOR 3-Month tenor (symbol: JIBOR_3M) is a key benchmark used in floating-rate lending, interest rate swaps, and cash yield modeling. Teams that attempt to compile this data manually quickly encounter gaps, inconsistent frequencies, and off-calendar reporting—problems that introduce material risk into pricing and analytics. This article shows how to build a robust, production-grade JIBOR_3M interest rate tracker using interestratesapi.com, focusing on multi-year time series retrieval, point-in-time lookups, OHLC aggregation for charting, and a Python data pipeline for storage and analysis.
Why a dedicated Interest Rates API solves critical business problems
Developers, economists, and data engineers often face four recurring problems when sourcing interbank rate data:
- Coverage and consistency: JIBOR_3M is a monthly series with country-specific calendars. Manual scraping or ad-hoc spreadsheets produce missing months or irregular timestamps that break downstream models.
- Point-in-time accuracy: Historical valuations, backtests, and regulatory audits require exact values as of a given date—often falling on weekends or holidays. Without an authoritative data source that handles calendar nuances, results become non-reproducible.
- Aggregation for analytics: Technical users frequently want OHLC-style summaries (open, high, low, close) for trend visualization and volatility inspection, which aren’t readily available without computing them from base data.
- Engineering economics: Building ingestion, quality checks, resampling, and export pipelines from scratch adds months of effort. A unified API reduces time-to-value, enabling teams to focus on modeling and product differentiation.
interestratesapi.com offers a focused set of endpoints designed for interest rate research and production workloads. The platform helps you:
- Retrieve multi-year series with precise start/end semantics and currency metadata.
- Request historical snapshots that respect monthly symbol conventions and non-trading days.
- Generate OHLC aggregates on the fly for charting, dashboards, and exploratory analysis.
- Compare relative loan cost impacts between reference rates using a simple conversion tool.
Throughout this guide, all examples exclusively use the JIBOR_3M symbol and compatible peers from the same symbol catalogue. For full platform details and additional endpoints, visit:
Explore Interest Rates API features
Get started with Interest Rates API
Core data model and symbol: JIBOR_3M (JIBOR 3-Month, interbank, IDR, monthly)
JIBOR_3M is an interbank tenor representing a 3-month rate in Indonesia’s IDR market, generally maintained at a monthly frequency. This cadence has implications for data retrieval and interpretation:
- Frequency: Monthly symbols do not report values for every calendar day. When querying time ranges, expect sparse rows (one per month, or last reported day in the month).
- Historical lookup behavior: For monthly series, a query for any date within the month will resolve to the last day with data in that month (e.g., if you query mid-month and the latest record is month-end, the API returns the month-end observation).
- Aggregation: OHLC for monthly symbols summarizes the values found within each aggregation window (weekly, monthly, quarterly). For monthly-native symbols, monthly OHLC often yields open and close equal to the month’s single observation—unless the series presents multiple dated values within the month.
Next, we will cover the endpoints exposed by interestratesapi.com, with emphasis on the multi-year time series workflow and robust handling of monthly JIBOR_3M data. All requests below use GET and the base URL:
https://interestratesapi.com/api/v1/
In each example, the api_key query parameter is included with the request.
Endpoint overview and practical use cases
interestratesapi.com provides seven core endpoints relevant to JIBOR_3M analytics:
- /symbols: Discover available rate symbols and metadata (category, currency, frequency).
- /latest: Retrieve the latest available value for one or multiple symbols.
- /historical: Fetch the value for a specific date, with calendar-aware handling for monthly series.
- /timeseries: Get a continuous slice of observations between two dates—ideal for multi-year datasets and backtesting.
- /fluctuation: Summarize changes over a defined window, including highs, lows, and percent change.
- /ohlc: Compute open-high-low-close aggregates over weekly, monthly, or quarterly periods for charting and regime analysis.
- /convert: Compare simple loan interest costs implied by two symbols’ latest rates.
Below we go deep on each, with JIBOR_3M as our primary symbol.
Time series first: /timeseries for multi-year JIBOR_3M histories
The /timeseries endpoint is the backbone for analytics pipelines. It retrieves a contiguous range of observations, returning a nested dictionary keyed by symbol and then by date. For JIBOR_3M—defined as a monthly series—expect one observation per month (typically aligned to the last reporting day within the month).
Business value and usage patterns
- Backtesting loan repricing: Pull 5–10 years of JIBOR_3M to simulate floating coupon resets.
- Risk models: Calibrate term structure dynamics or macro regressions using monthly JIBOR_3M levels.
- Reporting: Populate BI dashboards with consistent time slices for trend visualization.
Key request parameters and behavior
- start, end (Y-m-d): Defines the inclusive date window. For monthly series, data points appear only on the last available day in each month within the range.
- symbols: Comma-separated list; we will focus on JIBOR_3M.
- base (optional): Filter by currency; generally not needed when the symbol is known.
cURL example: multi-year JIBOR_3M time series
curl "https://interestratesapi.com/api/v1/timeseries?start=2018-01-01&end=2026-09-18&symbols=JIBOR_3M&api_key=YOUR_KEY"
Python (requests) example
import requests
url = "https://interestratesapi.com/api/v1/timeseries"
params = {
"start": "2018-01-01",
"end": "2026-09-18",
"symbols": "JIBOR_3M",
"api_key": "YOUR_KEY"
}
resp = requests.get(url, params=params)
data = resp.json()
print(data)
JavaScript (fetch) example
const url = "https://interestratesapi.com/api/v1/timeseries?start=2018-01-01&end=2026-09-18&symbols=JIBOR_3M&api_key=YOUR_KEY";
const response = await fetch(url);
const data = await response.json();
console.log(data);
PHP example
<?php
$base = "https://interestratesapi.com/api/v1/timeseries";
$query = http_build_query([
"start" => "2018-01-01",
"end" => "2026-09-18",
"symbols" => "JIBOR_3M",
"api_key" => "YOUR_KEY"
]);
$url = $base . "?" . $query;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$out = curl_exec($ch);
curl_close($ch);
$data = json_decode($out, true);
print_r($data);
?>
Sample JSON response and field interpretation
{
"success": true,
"base": "IDR",
"start_date": "2018-01-01",
"end_date": "2026-09-18",
"rates": {
"JIBOR_3M": {
"2018-01-31": 5.35,
"2018-02-28": 5.38,
"2018-03-29": 5.40,
"2018-04-30": 5.48,
"2018-05-31": 5.60,
"2018-06-29": 5.82,
"2018-07-31": 5.92,
"2018-08-31": 6.05,
"2018-09-28": 6.18,
"2018-10-31": 6.35,
"2018-11-30": 6.55,
"2018-12-31": 6.70,
"2019-01-31": 6.68,
"2019-02-28": 6.62,
"2019-03-29": 6.53,
"2019-04-30": 6.50,
"2019-05-31": 6.50,
"2019-06-28": 6.45,
"2019-07-31": 6.35,
"2019-08-30": 6.18,
"2019-09-30": 6.02,
"2019-10-31": 5.92,
"2019-11-29": 5.80,
"2019-12-31": 5.70,
"2020-01-31": 5.65,
"2020-02-28": 5.58,
"2020-03-31": 5.55,
"2020-04-30": 5.45,
"2020-05-29": 5.38,
"2020-06-30": 5.30,
"2020-07-31": 5.22,
"2020-08-31": 5.15,
"2020-09-30": 5.10,
"2020-10-30": 5.08,
"2020-11-30": 5.00,
"2020-12-31": 4.92,
"2021-01-29": 4.85,
"2021-02-26": 4.78,
"2021-03-31": 4.75,
"2021-04-30": 4.72,
"2021-05-31": 4.70,
"2021-06-30": 4.68,
"2021-07-30": 4.65,
"2021-08-31": 4.63,
"2021-09-30": 4.62,
"2021-10-29": 4.60,
"2021-11-30": 4.60,
"2021-12-31": 4.58,
"2022-01-31": 4.60,
"2022-02-28": 4.68,
"2022-03-31": 4.80,
"2022-04-29": 4.95,
"2022-05-31": 5.10,
"2022-06-30": 5.25,
"2022-07-29": 5.35,
"2022-08-31": 5.45,
"2022-09-30": 5.55,
"2022-10-31": 5.70,
"2022-11-30": 5.85,
"2022-12-30": 6.00,
"2023-01-31": 6.05,
"2023-02-28": 6.10,
"2023-03-31": 6.15,
"2023-04-28": 6.18,
"2023-05-31": 6.20,
"2023-06-30": 6.22,
"2023-07-31": 6.22,
"2023-08-31": 6.20,
"2023-09-29": 6.18,
"2023-10-31": 6.15,
"2023-11-30": 6.12,
"2023-12-29": 6.10,
"2024-01-31": 6.05,
"2024-02-29": 6.02,
"2024-03-29": 6.00,
"2024-04-30": 5.98,
"2024-05-31": 5.95,
"2024-06-28": 5.93,
"2024-07-31": 5.90,
"2024-08-30": 5.88,
"2024-09-18": 5.86
}
},
"frequencies": { "JIBOR_3M": "monthly" },
"currencies": { "JIBOR_3M": "IDR" }
}
Interpretation:
- success: Boolean request status.
- base: Currency context for the series (IDR for JIBOR_3M).
- start_date, end_date: Echoed limits for the returned slice.
- rates: Nested object mapping symbol to a date:value table. For monthly series, dates usually correspond to the final reporting day in each month.
- frequencies: Declares the sampling frequency; JIBOR_3M is monthly.
- currencies: Confirms the currency used by the symbol (IDR).
Performance and best practices for /timeseries
- Query precise windows: Limit the start and end to the exact backtest range to reduce payload size.
- Handle sparse dates: Do not forward-fill monthly rates into daily views without a clear business rule—e.g., use month-end carry or observation-lag conventions for coupon accruals.
- Cache results: Persist canonical monthly series in a local store and refresh as new months close.
For hands-on exploration and further capabilities, visit:
Explore Interest Rates API features
Point-in-time accuracy with /historical (including weekends and month-end logic)
Audit requests, PnL explain, and regulatory reporting often require the JIBOR_3M rate “as of” a particular calendar date. The /historical endpoint returns the correct monthly value even if the requested date falls on a weekend or a non-reporting day. For monthly symbols like JIBOR_3M, the endpoint maps any date within a month to the last day with data available for that month.
Common scenarios
- Month-end valuations: Ask for the rate on 2024-08-15; receive the month’s last reported value (e.g., 2024-08-30).
- Repricing checks: Confirm the exact rate governing a coupon reset date within a monthly observation cycle.
cURL example
curl "https://interestratesapi.com/api/v1/historical?date=2024-08-15&symbols=JIBOR_3M&api_key=YOUR_KEY"
Python (requests) example
import requests
resp = requests.get(
"https://interestratesapi.com/api/v1/historical",
params={"date": "2024-08-15", "symbols": "JIBOR_3M", "api_key": "YOUR_KEY"}
)
print(resp.json())
JavaScript (fetch) example
const url = "https://interestratesapi.com/api/v1/historical?date=2024-08-15&symbols=JIBOR_3M&api_key=YOUR_KEY";
const response = await fetch(url);
const data = await response.json();
console.log(data);
PHP example
<?php
$url = "https://interestratesapi.com/api/v1/historical?date=2024-08-15&symbols=JIBOR_3M&api_key=YOUR_KEY";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$out = curl_exec($ch);
curl_close($ch);
print_r(json_decode($out, true));
?>
Sample JSON response and interpretation
{
"success": true,
"date": "2024-08-15",
"base": "IDR",
"rates": { "JIBOR_3M": 5.88 },
"currencies": { "JIBOR_3M": "IDR" }
}
Notes:
- date: The requested date. For monthly symbols, the rate corresponds to the last day within that month that has data (not necessarily the exact date you queried).
- rates: A single numeric value for JIBOR_3M on that month’s official record.
Implementation guidance:
- Always log the requested date and interpret results as “effective for this month’s last observed trading day.”
- For audit trails, pair /historical calls with your valuation timestamp and snapshot IDs in your data lake.
OHLC aggregation with /ohlc for candlestick analytics and charting
Candlestick-style viewers are popular in rate analytics dashboards for identifying regime shifts and volatility. The /ohlc endpoint computes open, high, low, and close values for an aggregation period derived from the underlying series. For JIBOR_3M, which is monthly, using period=monthly typically yields one observation per month, so OHLC may have equal open and close unless multiple dated values exist within that month’s window. However, computing OHLC over broader periods (e.g., quarterly) can capture intra-quarter highs and lows across the composing months.
cURL example
curl "https://interestratesapi.com/api/v1/ohlc?symbols=JIBOR_3M&period=quarterly&start=2022-01-01&end=2024-12-31&api_key=YOUR_KEY"
Python (requests) example
import requests
resp = requests.get(
"https://interestratesapi.com/api/v1/ohlc",
params={
"symbols": "JIBOR_3M",
"period": "quarterly",
"start": "2022-01-01",
"end": "2024-12-31",
"api_key": "YOUR_KEY"
}
)
print(resp.json())
JavaScript (fetch) example
const url = "https://interestratesapi.com/api/v1/ohlc?symbols=JIBOR_3M&period=quarterly&start=2022-01-01&end=2024-12-31&api_key=YOUR_KEY";
const response = await fetch(url);
const data = await response.json();
console.log(data);
PHP example
<?php
$base = "https://interestratesapi.com/api/v1/ohlc";
$query = http_build_query([
"symbols" => "JIBOR_3M",
"period" => "quarterly",
"start" => "2022-01-01",
"end" => "2024-12-31",
"api_key" => "YOUR_KEY"
]);
$url = $base . "?" . $query;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$out = curl_exec($ch);
curl_close($ch);
print_r(json_decode($out, true));
?>
Sample JSON response and field meanings
{
"success": true,
"period": "quarterly",
"start_date": "2022-01-01",
"end_date": "2024-12-31",
"rates": {
"JIBOR_3M": [
{
"period": "2022-Q1",
"open": 4.60,
"high": 4.95,
"low": 4.60,
"close": 4.95,
"data_points": 3
},
{
"period": "2022-Q2",
"open": 5.10,
"high": 5.25,
"low": 5.10,
"close": 5.25,
"data_points": 3
},
{
"period": "2022-Q3",
"open": 5.35,
"high": 5.55,
"low": 5.35,
"close": 5.55,
"data_points": 3
},
{
"period": "2022-Q4",
"open": 5.70,
"high": 6.00,
"low": 5.70,
"close": 6.00,
"data_points": 3
}
]
}
}
Interpretation:
- period: Aggregation granularity (quarterly here).
-
rates: Array of OHLC objects per symbol, each with:
- period: The time bucket label (e.g., 2022-Q3).
- open: First available value in the period.
- high: Maximum value within the period.
- low: Minimum value within the period.
- close: Last available value in the period.
- data_points: Count of raw observations contributing to the OHLC calculation.
Chart.js candlestick integration snippet
Below is a minimal browser snippet that fetches OHLC for JIBOR_3M and prepares datasets for a candlestick chart. You can use a financial charting plugin for Chart.js (e.g., chartjs-chart-financial) to render the OHLC series. The code focuses on transforming the API response into the structure such plugins expect.
<!-- Assumes Chart.js and a candlestick plugin are included on the page -->
<script>
async function renderJIBOR3MChart() {
const url = "https://interestratesapi.com/api/v1/ohlc?symbols=JIBOR_3M&period=monthly&start=2022-01-01&end=2026-09-18&api_key=YOUR_KEY";
const res = await fetch(url);
const json = await res.json();
const ohlc = json.rates["JIBOR_3M"].map(row => ({
t: row.period, // e.g., "2024-03" or "2022-10"
o: row.open,
h: row.high,
l: row.low,
c: row.close
}));
const ctx = document.getElementById("jibor3mChart").getContext("2d");
const chart = new Chart(ctx, {
type: "candlestick",
data: {
datasets: [{
label: "JIBOR 3M (Monthly OHLC)",
data: ohlc
}]
},
options: {
responsive: true,
parsing: false,
scales: {
x: { type: "category" },
y: { type: "linear", title: { display: true, text: "Percent" } }
}
}
});
}
renderJIBOR3MChart();
</script>
<canvas id="jibor3mChart" width="800" height="400"></canvas>
Practical notes:
- For monthly-native symbols, period=monthly may create candles with identical open and close, which is still useful for tracking level shifts month-to-month.
- Try period=quarterly to expose intra-quarter range (high/low) across the months inside each quarter.
Change analysis with /fluctuation: quantify trends and ranges
The /fluctuation endpoint computes summary statistics over a window—start and end values, absolute and percent change, highs, and lows. This is valuable for executive summaries, automated alerting, and contextual tooltips in dashboards.
cURL example
curl "https://interestratesapi.com/api/v1/fluctuation?start=2023-01-01&end=2024-12-31&symbols=JIBOR_3M&api_key=YOUR_KEY"
Python (requests) example
import requests
resp = requests.get(
"https://interestratesapi.com/api/v1/fluctuation",
params={"start": "2023-01-01", "end": "2024-12-31", "symbols": "JIBOR_3M", "api_key": "YOUR_KEY"}
)
print(resp.json())
JavaScript (fetch) example
const url = "https://interestratesapi.com/api/v1/fluctuation?start=2023-01-01&end=2024-12-31&symbols=JIBOR_3M&api_key=YOUR_KEY";
const response = await fetch(url);
const data = await response.json();
console.log(data);
PHP example
<?php
$url = "https://interestratesapi.com/api/v1/fluctuation?start=2023-01-01&end=2024-12-31&symbols=JIBOR_3M&api_key=YOUR_KEY";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$out = curl_exec($ch);
curl_close($ch);
print_r(json_decode($out, true));
?>
Sample JSON response and interpretation
{
"success": true,
"rates": {
"JIBOR_3M": {
"start_date": "2023-01-01",
"end_date": "2024-12-31",
"start_value": 6.05,
"end_value": 5.95,
"change": -0.10,
"change_pct": -1.65,
"high": 6.22,
"low": 5.90
}
}
}
Interpretation:
- start_value, end_value: Boundary values within the requested window; for monthly series, these correspond to the first and last available months in-range.
- change, change_pct: Absolute and percent change—ideal for headline KPIs.
- high, low: Range extremes in the period—useful for volatility flags and thresholds.
Latest snapshot with /latest: monitoring and alerting pipelines
While most analytics hinge on historical series, operations teams also need the current JIBOR_3M rate for live dashboards, alerts, and pricing pages. The /latest endpoint returns the most recent value and date for one or many symbols.
cURL example
curl "https://interestratesapi.com/api/v1/latest?symbols=JIBOR_3M&api_key=YOUR_KEY"
Python (requests) example
import requests
resp = requests.get(
"https://interestratesapi.com/api/v1/latest",
params={"symbols": "JIBOR_3M", "api_key": "YOUR_KEY"}
)
print(resp.json())
JavaScript (fetch) example
const response = await fetch("https://interestratesapi.com/api/v1/latest?symbols=JIBOR_3M&api_key=YOUR_KEY");
const data = await response.json();
console.log(data);
PHP example
<?php
$url = "https://interestratesapi.com/api/v1/latest?symbols=JIBOR_3M&api_key=YOUR_KEY";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$out = curl_exec($ch);
curl_close($ch);
print_r(json_decode($out, true));
?>
Sample JSON response and interpretation
{
"success": true,
"date": "2026-09-18",
"base": "IDR",
"rates": {
"JIBOR_3M": 5.86
},
"dates": {
"JIBOR_3M": "2026-09-18"
},
"currencies": {
"JIBOR_3M": "IDR"
}
}
Interpretation:
- date: The system-wide “as-of” date for the payload.
- rates: Map of symbol to current value.
- dates: Per-symbol last update date—use this for staleness detection in monitoring.
- currencies: Currency per symbol; for JIBOR_3M it is IDR.
Symbol discovery with /symbols
When integrating new analyses, use /symbols to enumerate available identifiers and their metadata. This helps you validate that JIBOR_3M is categorized as interbank, confirm its currency, and check frequency.
cURL example
curl "https://interestratesapi.com/api/v1/symbols?category=interbank&base=IDR&api_key=YOUR_KEY"
Python (requests) example
import requests
resp = requests.get(
"https://interestratesapi.com/api/v1/symbols",
params={"category": "interbank", "base": "IDR", "api_key": "YOUR_KEY"}
)
print(resp.json())
JavaScript (fetch) example
const url = "https://interestratesapi.com/api/v1/symbols?category=interbank&base=IDR&api_key=YOUR_KEY";
const response = await fetch(url);
const data = await response.json();
console.log(data);
PHP example
<?php
$url = "https://interestratesapi.com/api/v1/symbols?category=interbank&base=IDR&api_key=YOUR_KEY";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$out = curl_exec($ch);
curl_close($ch);
print_r(json_decode($out, true));
?>
Sample JSON response and interpretation
{
"success": true,
"count": 1,
"symbols": [
{
"symbol": "JIBOR_3M",
"name": "JIBOR 3-Month",
"category": "interbank",
"country_code": "ID",
"currency_code": "IDR",
"frequency": "monthly",
"description": "Jakarta Interbank Offered Rate, 3-month tenor"
}
]
}
Interpretation:
- frequency: Guides your resampling logic and downstream storage partitioning.
- currency_code: Useful for multi-currency dashboards and labeling in BI tools.
Comparative cost modeling with /convert
The /convert endpoint estimates the total interest cost of a simple loan priced at the latest rate of two symbols and compares them. This can contextualize JIBOR_3M by contrasting it with another benchmark. For example, compare JIBOR_3M with ECB_MRO over a 12-month term to quantify interest differential.
cURL example
curl "https://interestratesapi.com/api/v1/convert?from=JIBOR_3M&to=ECB_MRO&amount=5000000&term_months=12&api_key=YOUR_KEY"
Python (requests) example
import requests
params = {
"from": "JIBOR_3M",
"to": "ECB_MRO",
"amount": 5000000,
"term_months": 12,
"api_key": "YOUR_KEY"
}
resp = requests.get("https://interestratesapi.com/api/v1/convert", params=params)
print(resp.json())
JavaScript (fetch) example
const url = "https://interestratesapi.com/api/v1/convert?from=JIBOR_3M&to=ECB_MRO&amount=5000000&term_months=12&api_key=YOUR_KEY";
const res = await fetch(url);
const data = await res.json();
console.log(data);
PHP example
<?php
$url = "https://interestratesapi.com/api/v1/convert?from=JIBOR_3M&to=ECB_MRO&amount=5000000&term_months=12&api_key=YOUR_KEY";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$out = curl_exec($ch);
curl_close($ch);
print_r(json_decode($out, true));
?>
Sample JSON response and interpretation
{
"success": true,
"amount": 5000000,
"term_months": 12,
"from": {
"symbol": "JIBOR_3M",
"rate": 5.86,
"date": "2026-09-18",
"total_interest": 293000.00,
"total_payment": 5293000.00
},
"to": {
"symbol": "ECB_MRO",
"rate": 4.50,
"date": "2026-09-18",
"total_interest": 225000.00,
"total_payment": 5225000.00
},
"difference": {
"rate_spread": 1.36,
"interest_saved": 68000.00
}
}
Interpretation:
- rate: Latest available rate for each symbol at query time.
- total_interest, total_payment: Simple loan cost estimate—use to explain rate sensitivity to business stakeholders.
- difference.rate_spread: Indicates the spread between the two benchmarks.
Building a robust Python data pipeline for JIBOR_3M (fetch → pandas → CSV/Parquet)
Production analytics require durable storage, consistent schemas, and reproducible transforms. The following end-to-end example downloads a multi-year JIBOR_3M series, constructs a pandas DataFrame, handles sparse monthly dates correctly, and exports the dataset as CSV and Parquet. The example also includes minimal validation and a convenience function for appending new months.
import os
import sys
import json
import time
import requests
import pandas as pd
from datetime import datetime, date
API_BASE = "https://interestratesapi.com/api/v1"
API_KEY = "YOUR_KEY"
SYMBOL = "JIBOR_3M"
def fetch_timeseries(start: str, end: str, symbol: str = SYMBOL) -> dict:
url = f"{API_BASE}/timeseries"
params = {"start": start, "end": end, "symbols": symbol, "api_key": API_KEY}
r = requests.get(url, params=params, timeout=30)
r.raise_for_status()
payload = r.json()
if not payload.get("success", False):
raise RuntimeError(f"API error: {payload.get('error', 'unknown')}")
return payload
def to_dataframe(timeseries_payload: dict, symbol: str = SYMBOL) -> pd.DataFrame:
rates = timeseries_payload["rates"][symbol]
# Convert to DataFrame with proper date index
df = (pd.Series(rates, name="value")
.rename_axis("date")
.reset_index())
df["date"] = pd.to_datetime(df["date"], format="%Y-%m-%d", utc=False)
df = df.sort_values("date").set_index("date")
df["symbol"] = symbol
df["currency"] = timeseries_payload.get("currencies", {}).get(symbol)
df["frequency"] = timeseries_payload.get("frequencies", {}).get(symbol)
return df[["symbol", "currency", "frequency", "value"]]
def export_csv_parquet(df: pd.DataFrame, out_dir: str = "data"):
os.makedirs(out_dir, exist_ok=True)
csv_path = os.path.join(out_dir, "JIBOR_3M_timeseries.csv")
parquet_path = os.path.join(out_dir, "JIBOR_3M_timeseries.parquet")
df.to_csv(csv_path, index=True, date_format="%Y-%m-%d")
df.to_parquet(parquet_path, index=True)
print(f"Saved CSV: {csv_path}")
print(f"Saved Parquet: {parquet_path}")
def append_new_data(existing_df: pd.DataFrame, last_loaded: str, symbol: str = SYMBOL) -> pd.DataFrame:
# Fetch from the day after last_loaded to today
start = (pd.to_datetime(last_loaded) + pd.Timedelta(days=1)).strftime("%Y-%m-%d")
end = date.today().strftime("%Y-%m-%d")
payload = fetch_timeseries(start, end, symbol)
new_df = to_dataframe(payload, symbol)
combined = pd.concat([existing_df, new_df]).drop_duplicates(subset=["symbol"], keep="last")
# For monthly frequency, duplicates by month should not occur unless re-queries overlap
combined = combined[~combined.index.duplicated(keep="last"] # ensure unique index
return combined.sort_index()
if __name__ == "__main__":
# Initial full load
start = "2018-01-01"
end = date.today().strftime("%Y-%m-%d")
payload = fetch_timeseries(start, end, SYMBOL)
df = to_dataframe(payload, SYMBOL)
# Basic validation
if df["value"].isna().any():
raise ValueError("Missing values encountered; verify symbol frequency and date range.")
# Monthly frequency sanity check
freq = df["frequency"].dropna().unique()
if len(freq) > 1 or (len(freq) == 1 and freq[0] != "monthly"):
print(f"Warning: Expected monthly frequency, got {freq}", file=sys.stderr)
# Export to both formats
export_csv_parquet(df)
# Example incremental update on next run:
# df2 = append_new_data(df, last_loaded=df.index.max().strftime("%Y-%m-%d"))
# export_csv_parquet(df2)
Design notes:
- Use the frequency field in the response to parameterize downstream logic. For JIBOR_3M, monthly implies sparse month-end stamps.
- Keep the date index as an actual datetime type for joins (e.g., merging with inflation or credit spreads).
- Export both CSV (human-readable) and Parquet (columnar, analytics-friendly) to serve diverse consumers.
Handling time series pitfalls: calendars, missing dates, and data_points
Rates data often defy simple “daily” assumptions:
- Monthly cadence: JIBOR_3M is monthly. Avoid resampling naively to daily without clear intent (e.g., using last-observation-carried-forward for accrual approximations).
- Missing months: Rare but possible due to structural changes or data availability. Always build guards for month gaps and consider interpolation only if your methodology justifies it.
- Non-trading days: For /historical queries, weekends and holidays map to the month’s last reported day—document this behavior in your pipeline’s data dictionary.
- OHLC data_points: The data_points in /ohlc tell you how many raw observations formed each candle. For monthly-native symbols, monthly OHLC typically has data_points=1; quarterly often equals 3. Use this to flag anomalous aggregation (e.g., data_points=0 should not occur; investigate upstream).
Practical mitigation strategies:
- Enforce monotonic dates: Sort by date and assert strictly increasing order; alert on duplicates.
- Staleness check: Use /latest “dates” mapping and compare against today’s date; if the latest month-end has not yet arrived, do not advance accrual periods prematurely.
- Schema contracts: Include symbol, currency, and frequency columns in your core tables so data consumers can quickly reason about the time axis.
End-to-end example workflow: discovery → latest → historical → timeseries → ohlc → fluctuation
Putting it all together, consider an application that needs:
- To discover metadata for JIBOR_3M.
- Show the current JIBOR_3M on a dashboard.
- Provide an “as-of” lookup for a user-specified date.
- Load the past five years for a trend chart and CSV export.
- Display monthly candlesticks and quarterly performance summaries.
With interestratesapi.com, you chain the endpoints:
- /symbols to validate that JIBOR_3M exists, is interbank, currency=IDR, frequency=monthly.
- /latest to populate the KPI card with the most recent value and update date.
- /historical for precise point-in-time retrieval based on the user’s chosen date.
- /timeseries to fetch multi-year history and power trend and export features.
- /ohlc for visualization-ready candles.
- /fluctuation for summary statistics (change, highs, lows) to annotate charts.
You can also add /convert to contextualize cost differences relative to another benchmark, such as ECB_MRO, for education or cross-market analysis.
Detailed JSON example: complete five-year pack for JIBOR_3M
Below is a composed example that illustrates a representative five-year workflow. Although most applications will call endpoints separately, this block shows realistic field shapes and values you can expect as you stitch data across endpoints. Use it as a reference when building your deserializers and models.
{
"symbols": {
"success": true,
"count": 1,
"symbols": [
{
"symbol": "JIBOR_3M",
"name": "JIBOR 3-Month",
"category": "interbank",
"country_code": "ID",
"currency_code": "IDR",
"frequency": "monthly",
"description": "Jakarta Interbank Offered Rate, 3-month tenor"
}
]
},
"latest": {
"success": true,
"date": "2026-09-18",
"base": "IDR",
"rates": { "JIBOR_3M": 5.86 },
"dates": { "JIBOR_3M": "2026-09-18" },
"currencies": { "JIBOR_3M": "IDR" }
},
"historical": {
"success": true,
"date": "2024-08-15",
"base": "IDR",
"rates": { "JIBOR_3M": 5.88 },
"currencies": { "JIBOR_3M": "IDR" }
},
"timeseries": {
"success": true,
"base": "IDR",
"start_date": "2021-01-01",
"end_date": "2026-09-18",
"rates": {
"JIBOR_3M": {
"2021-01-29": 4.85,
"2021-02-26": 4.78,
"2021-03-31": 4.75,
"2021-04-30": 4.72,
"2021-05-31": 4.70,
"2021-06-30": 4.68,
"2021-07-30": 4.65,
"2021-08-31": 4.63,
"2021-09-30": 4.62,
"2021-10-29": 4.60,
"2021-11-30": 4.60,
"2021-12-31": 4.58,
"2022-01-31": 4.60,
"2022-02-28": 4.68,
"2022-03-31": 4.80,
"2022-04-29": 4.95,
"2022-05-31": 5.10,
"2022-06-30": 5.25,
"2022-07-29": 5.35,
"2022-08-31": 5.45,
"2022-09-30": 5.55,
"2022-10-31": 5.70,
"2022-11-30": 5.85,
"2022-12-30": 6.00,
"2023-01-31": 6.05,
"2023-02-28": 6.10,
"2023-03-31": 6.15,
"2023-04-28": 6.18,
"2023-05-31": 6.20,
"2023-06-30": 6.22,
"2023-07-31": 6.22,
"2023-08-31": 6.20,
"2023-09-29": 6.18,
"2023-10-31": 6.15,
"2023-11-30": 6.12,
"2023-12-29": 6.10,
"2024-01-31": 6.05,
"2024-02-29": 6.02,
"2024-03-29": 6.00,
"2024-04-30": 5.98,
"2024-05-31": 5.95,
"2024-06-28": 5.93,
"2024-07-31": 5.90,
"2024-08-30": 5.88,
"2024-09-18": 5.86
}
},
"frequencies": { "JIBOR_3M": "monthly" },
"currencies": { "JIBOR_3M": "IDR" }
},
"ohlc": {
"success": true,
"period": "monthly",
"start_date": "2024-01-01",
"end_date": "2024-09-18",
"rates": {
"JIBOR_3M": [
{ "period": "2024-01", "open": 6.05, "high": 6.05, "low": 6.05, "close": 6.05, "data_points": 1 },
{ "period": "2024-02", "open": 6.02, "high": 6.02, "low": 6.02, "close": 6.02, "data_points": 1 },
{ "period": "2024-03", "open": 6.00, "high": 6.00, "low": 6.00, "close": 6.00, "data_points": 1 },
{ "period": "2024-04", "open": 5.98, "high": 5.98, "low": 5.98, "close": 5.98, "data_points": 1 },
{ "period": "2024-05", "open": 5.95, "high": 5.95, "low": 5.95, "close": 5.95, "data_points": 1 },
{ "period": "2024-06", "open": 5.93, "high": 5.93, "low": 5.93, "close": 5.93, "data_points": 1 },
{ "period": "2024-07", "open": 5.90, "high": 5.90, "low": 5.90, "close": 5.90, "data_points": 1 },
{ "period": "2024-08", "open": 5.88, "high": 5.88, "low": 5.88, "close": 5.88, "data_points": 1 }
]
}
},
"fluctuation": {
"success": true,
"rates": {
"JIBOR_3M": {
"start_date": "2024-01-01",
"end_date": "2024-09-18",
"start_value": 6.05,
"end_value": 5.86,
"change": -0.19,
"change_pct": -3.14,
"high": 6.05,
"low": 5.86
}
}
}
}
Error handling and troubleshooting
Robust production code anticipates validation issues and empty windows. Here are common error scenarios and recommended handling patterns:
- 401 Unauthorized: Typically means the request was not accepted. Ensure you are passing required parameters properly.
- 403 Forbidden: The account may not have permissions for the requested resource. Surface a clear message to the operator and retry later if appropriate.
- 404 Not Found: No symbols matched or no data exists for the requested date/range. Inspect the error’s details if present, and prompt the user to adjust the date window or symbol.
- 422 Unprocessable Entity: Validation error such as wrong date format or invalid symbol. Sanitize user inputs and implement client-side checks for date formats (YYYY-MM-DD).
General strategies:
- Validate dates before making requests; reject malformed input early in your UI or CLI.
- When a 404 occurs on /historical for a date outside the symbol’s existence window, guide the user to try a range known to have data via /timeseries or /symbols metadata.
- Log error payloads (success=false, error=message), along with request parameters, to streamline support and debugging.
Sample error JSON
{
"success": false,
"error": "No data available for requested date",
"details": {
"symbol": "JIBOR_3M",
"available_range": {
"start": "2010-01-01",
"end": "2026-09-18"
}
}
}
Handling tip:
- If available_range is returned, update user inputs or auto-correct the date picker to fall within the valid interval.
Field-by-field mapping to application requirements
When designing your internal schema, you will commonly store:
- symbol (string): “JIBOR_3M”
- date (date): Index key; for monthly series, use month-end alignment unless your model requires transaction dates.
- value (float): Rate in percent.
- currency (string): “IDR”
- frequency (string): “monthly”
- source (string): “interestratesapi.com”
For OHLC, store the period label, open, high, low, close, and data_points. When building chart layers, these fields drive candle bodies and wicks. For KPI tiles, use /fluctuation change_pct to color-code improvements or deteriorations relative to a baseline.
Performance, reliability, and observability practices
To operate a dependable finance data workflow:
- Caching policy: For monthly series like JIBOR_3M, cache results per month and refresh after month-end close. This reduces repeated calls and ensures consistent snapshots across consumers.
- Retries with backoff: Implement idempotent retries on transient network errors. Since all endpoints use GET, replay is safe when requests time out.
- Health checks: Add a liveness probe that calls /latest for a known symbol (e.g., JIBOR_3M) and asserts the presence of rates and dates fields.
- Circuit breakers: If multiple calls fail in succession, halt downstream jobs and notify operators rather than ingesting partial results.
- Audit logging: Persist full request URLs (excluding sensitive values in logs) and response metadata like success and date ranges, to allow downstream traceability.
These engineering patterns ensure consistent, reproducible data—critical for auditability in finance contexts.
Additional implementation examples by endpoint
/symbols — list and filter catalog
Use filters to narrow the catalog to interbank IDR series and confirm JIBOR_3M attributes.
curl "https://interestratesapi.com/api/v1/symbols?category=interbank&base=IDR&api_key=YOUR_KEY"
import requests
resp = requests.get("https://interestratesapi.com/api/v1/symbols",
params={"category": "interbank", "base": "IDR", "api_key": "YOUR_KEY"})
print(resp.json())
const response = await fetch("https://interestratesapi.com/api/v1/symbols?category=interbank&base=IDR&api_key=YOUR_KEY");
console.log(await response.json());
<?php
$url = "https://interestratesapi.com/api/v1/symbols?category=interbank&base=IDR&api_key=YOUR_KEY";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$out = curl_exec($ch);
curl_close($ch);
echo $out;
?>
/latest — monitor current rate
curl "https://interestratesapi.com/api/v1/latest?symbols=JIBOR_3M&api_key=YOUR_KEY"
import requests
resp = requests.get("https://interestratesapi.com/api/v1/latest",
params={"symbols": "JIBOR_3M", "api_key": "YOUR_KEY"})
print(resp.json())
const res = await fetch("https://interestratesapi.com/api/v1/latest?symbols=JIBOR_3M&api_key=YOUR_KEY");
console.log(await res.json());
<?php
echo file_get_contents("https://interestratesapi.com/api/v1/latest?symbols=JIBOR_3M&api_key=YOUR_KEY");
?>
/historical — point-in-time
curl "https://interestratesapi.com/api/v1/historical?date=2025-06-15&symbols=JIBOR_3M&api_key=YOUR_KEY"
import requests
resp = requests.get("https://interestratesapi.com/api/v1/historical",
params={"date": "2025-06-15", "symbols": "JIBOR_3M", "api_key": "YOUR_KEY"})
print(resp.json())
const r = await fetch("https://interestratesapi.com/api/v1/historical?date=2025-06-15&symbols=JIBOR_3M&api_key=YOUR_KEY");
console.log(await r.json());
<?php
echo file_get_contents("https://interestratesapi.com/api/v1/historical?date=2025-06-15&symbols=JIBOR_3M&api_key=YOUR_KEY");
?>
/timeseries — multi-year history
curl "https://interestratesapi.com/api/v1/timeseries?start=2020-01-01&end=2026-09-18&symbols=JIBOR_3M&api_key=YOUR_KEY"
import requests
resp = requests.get("https://interestratesapi.com/api/v1/timeseries",
params={"start": "2020-01-01", "end": "2026-09-18", "symbols": "JIBOR_3M", "api_key": "YOUR_KEY"})
print(resp.json())
const t = await fetch("https://interestratesapi.com/api/v1/timeseries?start=2020-01-01&end=2026-09-18&symbols=JIBOR_3M&api_key=YOUR_KEY");
console.log(await t.json());
<?php
echo file_get_contents("https://interestratesapi.com/api/v1/timeseries?start=2020-01-01&end=2026-09-18&symbols=JIBOR_3M&api_key=YOUR_KEY");
?>
/fluctuation — change statistics
curl "https://interestratesapi.com/api/v1/fluctuation?start=2022-01-01&end=2026-09-18&symbols=JIBOR_3M&api_key=YOUR_KEY"
import requests
resp = requests.get("https://interestratesapi.com/api/v1/fluctuation",
params={"start": "2022-01-01", "end": "2026-09-18", "symbols": "JIBOR_3M", "api_key": "YOUR_KEY"})
print(resp.json())
const f = await fetch("https://interestratesapi.com/api/v1/fluctuation?start=2022-01-01&end=2026-09-18&symbols=JIBOR_3M&api_key=YOUR_KEY");
console.log(await f.json());
<?php
echo file_get_contents("https://interestratesapi.com/api/v1/fluctuation?start=2022-01-01&end=2026-09-18&symbols=JIBOR_3M&api_key=YOUR_KEY");
?>
/ohlc — aggregated candles
curl "https://interestratesapi.com/api/v1/ohlc?symbols=JIBOR_3M&period=monthly&start=2021-01-01&end=2026-09-18&api_key=YOUR_KEY"
import requests
resp = requests.get("https://interestratesapi.com/api/v1/ohlc",
params={"symbols": "JIBOR_3M", "period": "monthly", "start": "2021-01-01", "end": "2026-09-18", "api_key": "YOUR_KEY"})
print(resp.json())
const o = await fetch("https://interestratesapi.com/api/v1/ohlc?symbols=JIBOR_3M&period=monthly&start=2021-01-01&end=2026-09-18&api_key=YOUR_KEY");
console.log(await o.json());
<?php
echo file_get_contents("https://interestratesapi.com/api/v1/ohlc?symbols=JIBOR_3M&period=monthly&start=2021-01-01&end=2026-09-18&api_key=YOUR_KEY");
?>
/convert — loan cost comparison
curl "https://interestratesapi.com/api/v1/convert?from=JIBOR_3M&to=ECB_MRO&amount=10000000&term_months=24&api_key=YOUR_KEY"
import requests
resp = requests.get("https://interestratesapi.com/api/v1/convert",
params={"from": "JIBOR_3M", "to": "ECB_MRO", "amount": 10000000, "term_months": 24, "api_key": "YOUR_KEY"})
print(resp.json())
const c = await fetch("https://interestratesapi.com/api/v1/convert?from=JIBOR_3M&to=ECB_MRO&amount=10000000&term_months=24&api_key=YOUR_KEY");
console.log(await c.json());
<?php
echo file_get_contents("https://interestratesapi.com/api/v1/convert?from=JIBOR_3M&to=ECB_MRO&amount=10000000&term_months=24&api_key=YOUR_KEY");
?>
Real-world scenarios where JIBOR_3M data adds value
- Floating-rate loans in IDR: Price coupons off JIBOR_3M plus a spread, with /historical for reset dates and /timeseries for forward-looking accrual simulations.
- Treasury ALM: Integrate JIBOR_3M into liquidity ladders and behavioral models; use /fluctuation to track shifts over policy cycles.
- Macro analysis: Correlate JIBOR_3M trends with local policy moves and external benchmarks; use /ohlc to visualize regimes.
- Risk dashboards: /latest for heads-up displays; alert on change_pct thresholds using /fluctuation; embed Chart.js candlesticks for context.
Putting governance and controls into practice
Finance-grade data engineering stresses governance, auditability, and safe operations:
- Per-application segregation: Maintain separate applications and isolated storage for different business lines. Add clear ownership metadata to exported CSV/Parquet files.
- Roles and audit logs: Record who initiated data fetches and when; store request parameters and response checksums for reproducibility.
- Data locality: If your compliance framework differentiates environments, structure pipelines so sensitive exports remain within approved regions and systems.
- Observability: Capture metrics like fetch duration, payload size, and validation errors. Use dashboards to monitor changes in JIBOR_3M month over month.
By pairing these practices with interestratesapi.com’s focused endpoints, teams gain reliable, explainable interest rate data pipelines suitable for internal and external reporting.
Frequently asked implementation questions
How do I align JIBOR_3M with daily cashflows?
Use a carry-forward convention from the last available monthly observation until the next month’s record is in. Ensure this is stated in your methodology docs and mirrored in your accrual engine.
How do I manage partial months?
If mid-month data exists, /ohlc with period=monthly can reveal intra-month highs/lows. If the series is strictly month-end, mid-month values won’t exist—design your UI to avoid implying intra-month moves where none are recorded.
What if my historical date is outside the available range?
/historical will return a not found response. Query /symbols or /timeseries to discover the valid range, then adjust your lookup or prompt the user to pick a valid month.
Complete end-to-end notebook skeleton for analysts
Below is a compact Python skeleton to help analysts bootstrap a JIBOR_3M workbook, including timeseries fetch, OHLC for charts, and fluctuation stats for annotations.
import requests
import pandas as pd
import plotly.graph_objects as go
BASE = "https://interestratesapi.com/api/v1"
KEY = "YOUR_KEY"
SYMBOL = "JIBOR_3M"
def get_ts(start, end):
r = requests.get(f"{BASE}/timeseries",
params={"start": start, "end": end, "symbols": SYMBOL, "api_key": KEY})
r.raise_for_status()
js = r.json()
s = pd.Series(js["rates"][SYMBOL], name="value")
df = s.rename_axis("date").reset_index()
df["date"] = pd.to_datetime(df["date"])
return df.sort_values("date")
def get_ohlc(start, end, period="quarterly"):
r = requests.get(f"{BASE}/ohlc",
params={"symbols": SYMBOL, "period": period, "start": start, "end": end, "api_key": KEY})
r.raise_for_status()
js = r.json()
o = pd.DataFrame(js["rates"][SYMBOL])
return o
def get_fluct(start, end):
r = requests.get(f"{BASE}/fluctuation",
params={"start": start, "end": end, "symbols": SYMBOL, "api_key": KEY})
r.raise_for_status()
js = r.json()
return js["rates"][SYMBOL]
ts = get_ts("2019-01-01", "2026-09-18")
ohlc = get_ohlc("2022-01-01", "2026-09-18", period="quarterly")
fl = get_fluct("2024-01-01", "2026-09-18")
print("Fluctuation:", fl)
# Plotly quarterly candles
fig = go.Figure(data=[go.Candlestick(
x=ohlc["period"],
open=ohlc["open"],
high=ohlc["high"],
low=ohlc["low"],
close=ohlc["close"],
increasing_line_color="green",
decreasing_line_color="red",
name="JIBOR_3M"
)])
fig.update_layout(title="JIBOR_3M Quarterly OHLC", yaxis_title="Percent")
fig.show()
Conclusion: build reliable JIBOR_3M analytics in days, not months
Building a JIBOR_3M interest rate tracker from scratch—handling monthly cadence, point-in-time semantics, OHLC aggregations, and export pipelines—can derail core product schedules. interestratesapi.com provides a targeted set of GET endpoints with predictable schemas to accelerate development. Start with /timeseries for multi-year histories, rely on /historical for month-aware as-of values, use /ohlc for visualization-ready candles, and summarize movements with /fluctuation. Complement these with /latest for dashboards, /symbols for discovery, and /convert for intuitive loan-cost comparisons.
Whether you are a quant building factor models, a data engineer wiring pipelines, or a product developer shipping dashboards, you can assemble a production-grade JIBOR_3M workflow quickly and safely with this API.
To continue exploring and integrating, visit:




