RielSync
ENខ្មែរ
Log inGet started
Rates API

Fetch the NBC rate from your own system

One HTTP endpoint returns the official National Bank of Cambodia USD to KHR rate for any date RielSync holds. It is for developers and IT implementers who would rather pull the rate into a till, an ERP or a script than have RielSync write it in for them.

GET/public/v1/rates$3.99 a month as an add-on

Quick start

  1. Create a key in the console under Settings, API keys. Copy it when it is shown.
  2. Send it as a Bearer token on a GET request.
  3. Read rate from the JSON response.
curl
curl https://www.rielsync.com/public/v1/rates \
  -H "Authorization: Bearer rs_live_7f2c9a41e8b3d05c"
200 response
{
  "date": "2026-09-17",
  "rate": 4040,
  "base": "USD",
  "quote": "KHR",
  "rate_date": "2026-09-17",
  "carried_forward": false
}

Authentication

Every request carries a key created in the console. Keys look like rs_live_... and belong to the account, not to a single connection. An account can hold up to five at once, which is what makes rotation safe: create a new key, point your system at it, then delete the old one.

Header
Authorization: Bearer rs_live_7f2c9a41e8b3d05c

A missing or unrecognised key returns 401 invalid_key. A valid key on an account without the API returns 403 not_enabled.

Calling from a browser

A page on your own website can call the API directly, because the API accepts cross-origin requests from any origin. Only GET requests are allowed, and the key goes in the Authorization header. Your script can read X-RateLimit-Limit, X-RateLimit-Remaining and Retry-After from the response. Anything on a public page can be read by anyone who views it, including your key. Where you can, call the API from your own server and pass the rate on to the page.

Three ways to call it

/public/v1/ratesNo parameter. Returns the rate for today in Cambodian time. It never returns a date in the future.
/public/v1/rates?date=2026-09-19One day. If NBC published nothing for that day, the most recent earlier rate is returned with carried_forward: true.
/public/v1/rates?from=2026-09-01&to=2026-09-30A range, returned as a list of { date, rate }. Only days NBC published appear. Weekends and holidays are absent rather than padded.

Worked scenarios

Real situations, with the request and the response exactly as they come back. The dates and rates in these examples are example values.

Saturday, a till asks for today's rate

NBC published nothing for Saturday, so Friday's rate is still the effective rate for that day.
GET /public/v1/rates
Authorization: Bearer rs_live_7f2c9a41e8b3d05c
200 OK
{
  "date": "2026-09-19",
  "rate": 4043,
  "base": "USD",
  "quote": "KHR",
  "rate_date": "2026-09-18",
  "carried_forward": true
}
date is the Saturday the till asked about. rate_date is the Friday NBC actually published. carried_forward tells you the two differ, so you can show the source date on a receipt if you need to.

Friday evening, asking for Monday's rate

NBC publishes each day's rate the previous afternoon, so Monday is already available on Friday.
GET /public/v1/rates?date=2026-09-21
Authorization: Bearer rs_live_7f2c9a41e8b3d05c
200 OK
{
  "date": "2026-09-21",
  "rate": 4046,
  "base": "USD",
  "quote": "KHR",
  "rate_date": "2026-09-21",
  "carried_forward": false
}
Calling with no parameter on that same Friday still returns Friday. The endpoint never returns a future date unless you ask for it by name.

Asking for a date NBC has not published yet

A date further ahead than NBC has reached returns an error rather than a guess.
GET /public/v1/rates?date=2026-10-15
Authorization: Bearer rs_live_7f2c9a41e8b3d05c
404 Not Found
{
  "error": "rate_not_yet_published",
  "message": "No rate has been published for 2026-10-15."
}
Retry after NBC publishes, usually the afternoon before the date applies. Do not fall back to an older rate for a future date in your own code.

Month end, pulling a whole month

A range returns only the days NBC published.
GET /public/v1/rates?from=2026-09-01&to=2026-09-30
Authorization: Bearer rs_live_7f2c9a41e8b3d05c
200 OK
{
  "base": "USD",
  "quote": "KHR",
  "rates": [
    { "date": "2026-09-01", "rate": 4038 },
    { "date": "2026-09-02", "rate": 4039 },
    { "date": "2026-09-03", "rate": 4041 },
    { "date": "2026-09-04", "rate": 4040 },
    { "date": "2026-09-07", "rate": 4042 },
    { "date": "2026-09-08", "rate": 4042 },
    { "date": "2026-09-09", "rate": 4043 },
    { "date": "2026-09-10", "rate": 4041 },
    { "date": "2026-09-11", "rate": 4042 },
    { "date": "2026-09-14", "rate": 4044 },
    { "date": "2026-09-15", "rate": 4043 },
    { "date": "2026-09-16", "rate": 4041 },
    { "date": "2026-09-17", "rate": 4040 },
    { "date": "2026-09-18", "rate": 4043 },
    { "date": "2026-09-21", "rate": 4046 },
    { "date": "2026-09-22", "rate": 4045 },
    { "date": "2026-09-23", "rate": 4044 },
    { "date": "2026-09-25", "rate": 4044 },
    { "date": "2026-09-28", "rate": 4045 },
    { "date": "2026-09-29", "rate": 4046 },
    { "date": "2026-09-30", "rate": 4045 }
  ]
}
The 5th and 6th are a weekend, so they are absent rather than repeated. Twenty one entries for a month with nine non publication days is correct, not a gap in the data.

Our newest rate is too old to serve

If the most recent rate we hold is older than five business days, the endpoint errors instead of returning it.
GET /public/v1/rates
Authorization: Bearer rs_live_7f2c9a41e8b3d05c
503 Service Unavailable
{
  "error": "rate_stale",
  "message": "The most recent rate on file is older than 5 business days."
}
A quietly stale number posted onto invoices is worse than a request that fails loudly, so we return the error. Check the status page, and the rate resumes as soon as a fresh one is recorded.

Response fields

FieldTypeMeaning
datestringThe day the rate applies to, meaning the day you asked for.
ratenumberKHR per 1 USD, as published by NBC.
basestringAlways USD.
quotestringAlways KHR.
rate_datestringNBC's own date for the number being returned.
carried_forwardbooleanfalse when date and rate_date match. true when no rate was published for the day you asked about, so the most recent earlier rate is being used.

Errors

StatusCodeMeaning
401invalid_keyThe key is missing, malformed, or has been deleted.
403not_enabledThe key is valid but the account does not currently include the Rates API.
400invalid_dateA date parameter is not a real date in YYYY-MM-DD form.
400range_too_largeThe from and to span is longer than a single request may cover.
404rate_not_yet_publishedThe date is valid but NBC has not published a rate for it yet.
404no_rate_availableThe date is before anything we hold on record.
429rate_limitedToo many requests on this key. Wait the number of seconds in the Retry-After header, then retry.
503rate_staleOur newest rate is older than we are willing to serve.
500internal_errorSomething went wrong on our side. Try again shortly.

Limits and good behaviour

Each key is limited to 10 requests a second, 3,000 an hour and 20,000 a day. The hourly limit resets on the hour. The daily limit resets at midnight Cambodian time. The per-second limit is approximate. Going over a limit returns 429 rate_limited with a Retry-After header giving the number of seconds to wait. Successful responses include X-RateLimit-Limit and X-RateLimit-Remaining for the hourly limit.

LimitPer keyResets
Per second10Rolling, approximate
Per hour3,000On the hour
Per day20,000Midnight Cambodian time

Requests with a missing or invalid key are counted for each address. After 20 of them within 10 minutes, that address is blocked for 15 minutes and requests from it receive a 429 with a Retry-After header. Requests with a valid key are not counted.

Hold the rate, do not ask on every saleThe rate changes at most once a day. Fetch it when your till or server starts, keep it in memory or local storage, and refresh it once in the evening after NBC publishes. A busy shop should be making a handful of requests a day, not one per transaction.

The API can be used with SambaPOS by anyone comfortable wiring up an HTTP call, and the RielSync team is available to help. There is no ready-made plugin. POSFlow Solutions, our own point of sale company, has this built in, so POSFlow Solutions customers do not need to write any code.

In other languages

JavaScript
const res = await fetch(
  "https://www.rielsync.com/public/v1/rates",
  { headers: { Authorization: `Bearer ${key}` } }
);
const { rate, rate_date } = await res.json();
Python
r = requests.get(
    "https://www.rielsync.com/public/v1/rates",
    headers={"Authorization": f"Bearer {key}"},
)
rate = r.json()["rate"]

These are plain HTTP requests written out. RielSync does not ship a client library.

$3.99 a month on top of any plan, or $5.99 a month on its ownFull prices and what each plan includes are on the pricing page.
See pricing