Gold rates

Every rate in GOLD PRO is a price in rupees per gram. Nothing in the system is denominated per 10 grams, and the validation described on this page exists largely to catch a per-10-gram figure entered into a per-gram field.

Two kinds of rate coexist. The market rate is a universal figure with 24K, 22K and 18K values, either fetched live or entered by a tenant admin. A per-bank rate is the rate one partner bank publishes for gold loans, which trails the market and is a privately negotiated commercial term. A valuation is priced at one or the other, chosen per valuation through rateBasis. See Gold rates for the operational side.

How a rate is resolved

When a valuation is created, or when you read the latest rate, the server resolves the applicable market rate in three tiers and stops at the first hit.

  1. The tenant's own newest rate row. Any rate recorded against your tenant, regardless of how it was set.
  2. The newest online rate across the whole platform. This lookup is not scoped to a tenant. Only rows fetched from the live market qualify; a rate a tenant typed by hand is never shared, so one shop's figure can never price another shop's valuation.
  3. A live fetch, performed on demand. Used at cold start when nothing is stored anywhere. The result is returned but not saved.

The resolved value carries the rates, a timestamp and an origin of "tenant", "universal" or "live" — never another tenant's row identity.

Resolution fails, and returns nothing, only when no online rate exists anywhere on the platform and all three live providers fail. That is the one case where POST /api/valuations returns No gold rate available. Please try again shortly.

Tier 1 has a consequence worth planning for: once your tenant has recorded any rate at all, it stops falling back to the universal one. A manual rate pins your tenant to that figure until a newer row is appended — by another manual entry, or by the auto-sync job if you have it enabled. A brand-new tenant with no rows of its own tracks the platform's live rate automatically.

The live fetch tries GoldAPI.io first when GOLDAPI_API_KEY is configured, then goldprice.org, then CoinGecko. All three report international spot, so each is scaled by a domestic premium — import duty, GST and dealer margin — read from GOLD_DOMESTIC_PREMIUM and defaulting to 1.128. That gap moves with real-world duty and GST changes; it is an approximation of the Indian market rate, not a quote from it.

Read the applicable rate

GET /api/admin/gold-rates/latest

Returns the market rate per gram that a valuation created right now would be priced at, resolved through the three tiers above. Takes no parameters. Requires a staff principal — TENANT_ADMIN or TENANT_EMPLOYEE.

curl https://goldpro.shop/api/admin/gold-rates/latest \
  -H "Authorization: Bearer $TOKEN"
{
  "rates": {
    "24K": 9990,
    "22K": 9150,
    "20K": 8325,
    "18K": 7493
  },
  "lastUpdated": "2026-07-16T06:00:11.402Z",
  "source": "tenant"
}
FieldTypeDescription
ratesobjectRupees per gram, keyed by karat.
lastUpdatedstring or nullWhen the resolved rate was recorded, as an ISO timestamp. For a live on-demand fetch this is the moment of the fetch.
sourcestringWhich tier answered: "tenant", "universal", "live", or "none".

When resolution fails entirely the endpoint still returns 200, with every rate zeroed:

{
  "rates": { "24K": 0, "22K": 0, "20K": 0, "18K": 0 },
  "lastUpdated": null,
  "source": "none"
}

Check source rather than the status code. A source of "none" means POST /api/valuations will currently reject a valuation with No gold rate available. Please try again shortly., and zeroed rates are a sentinel for that condition, not a real price.

This endpoint reports a 20K rate per gram, computed as the 24K rate multiplied by 20/24. Nothing else in the system accepts it. POST /api/valuations rejects a 20K ornament outright with Unsupported purity "20K". Supported values are 24K, 22K, 18K. Do not offer 20K as a selectable purity, and do not price against this key. The valuation engine carries per-gram rates for 24K, 22K and 18K only.

StatusErrorCondition
401UnauthorizedNot a staff principal.

Record a market rate

POST /api/admin/gold-rates

Records your tenant's own market rate. Requires TENANT_ADMIN; a TENANT_EMPLOYEE receives 401 even though they can read the rate above.

NameTypeRequiredDescription
rate24knumberYesRupees per gram for 24K.
rate22knumberYesRupees per gram for 22K.
rate18knumberYesRupees per gram for 18K.

All three are required and validated together. A missing or non-numeric field becomes NaN and fails the positive-value check.

This appends a row rather than replacing one. Previous rates are retained and the newest wins on read, so the history of what you charged remains queryable. The row is written with a manual source, which makes it yours alone: it is never offered to another tenant as a fallback, and, per tier 1 above, it stops your tenant falling back to the platform rate.

curl -X POST https://goldpro.shop/api/admin/gold-rates \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "rate24k": 9990, "rate22k": 9150, "rate18k": 7493 }'
{
  "success": true,
  "rates": {
    "rate24k": 9990,
    "rate22k": 9150,
    "rate18k": 7493,
    "createdAt": "2026-07-16T06:00:11.402Z"
  }
}

The change is recorded in the audit log. Existing valuations are unaffected: each one snapshots the rates it was priced at, so a certificate reprinted after a rate change still shows the figures that produced it.

StatusErrorCondition
401UnauthorizedNot a TENANT_ADMIN.
400Gold rates must be greater than zero.Any rate is zero, negative, missing or non-numeric.
400Rates should follow 24K >= 22K >= 18K.The purity ordering is inverted.
400A band messageA rate is implausible for its 24K. See below.
500Internal Server ErrorAny other failure.

Rate validation

Every writer — the endpoint above, the admin screen, and the automated live sync — passes through one validator, so a rate the sync would write can never be one a human would be blocked from saving.

RuleRequirement
PositiveAll three rates must be greater than zero.
Ordering24K >= 22K >= 18K.
22K bandBetween 82% and 98% of the 24K rate. Pure 22K is about 91.6%.
18K bandBetween 65% and 85% of the 24K rate. Pure 18K is 75%.

Rates are rounded to two decimals on write. Ordering alone would let a units mistake through — a per-10-gram 24K of 123112 sits happily above a per-gram 22K of 12322 — so the bands are what actually catch it. A failure names the expected figure:

{
  "error": "22K rate (₹12322) looks wrong for a 24K of ₹123112. Expected roughly ₹112771/g. Check the rates are all per-gram."
}

The live-fetch paths add their own guard, rejecting any 24K outside ₹1,000 to ₹100,000 per gram before the shared validator runs, so a compromised or broken upstream price cannot be written verbatim and then price every tenant's valuations.

Per-bank rates

A bank publishes a single figure for gold loans: the 22K rate per gram. That is all the system stores for a bank. The 24K and 18K values are derived on read using the same fixed purity ratios as everywhere else: 24K is the 22K divided by 0.916, and 18K is 75% of that resulting 24K. A bank therefore has one source of truth, and its karats cannot drift apart.

There is exactly one current rate per tenant per bank. An edit overwrites it and is written to the audit log with the before and after figures. There is no history table and none is needed: every valuation snapshots liveGoldRate22k and bankGoldRate22k as they stood when it was priced, so past certificates stay reproducible after a rate changes.

A per-bank rate has no cross-tenant fallback of any kind. Unlike the market rate it is a privately negotiated commercial term, not a shared market figure, and one shop's rate for a bank is never visible to another.

To price a valuation at a bank's rate, send both a bankId and rateBasis: "bank" to POST /api/valuations. The bank's rate is looked up whether or not you ask for it, because the certificate prints the live and bank rates side by side regardless of which one applied. Asking for the bank basis when no rate is configured fails rather than silently repricing at the live rate:

{
  "error": "The selected bank has no gold rate configured. Set a rate for this bank, or value at the live rate."
}

Clearing a bank's rate makes subsequent valuations for that bank fall back to the live market rate. Valuations already made keep their snapshot.

There is no REST endpoint for reading or writing per-bank rates. They are managed only through the /admin/banks screen, which calls server actions restricted to TENANT_ADMIN. An integrator can select the bank basis on a valuation but cannot configure the rate over HTTP.

Input for a bank rate is a single 22K figure, validated on write: it must be greater than zero and between ₹500 and ₹100,000 per gram. Outside that range you get ₹108550 is outside the plausible range for a 22K rate. Enter the rate per gram, not per 10 grams. The derived 24K and 18K then pass the shared validator by construction.

List banks

GET /api/admin/banks

Returns the banks your tenant can attach to a valuation: your own bank templates plus the shared platform catalog, active ones only, ordered by name. Requires TENANT_ADMIN.

Use the ids returned here as bankId when creating a valuation.

curl https://goldpro.shop/api/admin/banks \
  -H "Authorization: Bearer $TOKEN"
[
  { "id": "clx8jy0k20001qw3fa1b2c3d4", "name": "Bank of Maharashtra" },
  { "id": "clx8jy0k20002qw3fe5f6g7h8", "name": "HDFC Bank" },
  { "id": "clx8jy0k20003qw3fi9j0k1l2", "name": "State Bank of India" }
]

Only id and name are returned. The list is de-duplicated case-insensitively after common suffixes are stripped, so "State Bank of India" and "State Bank of India Ltd" collapse to a single entry — the first in name order — even when both a shared template and your own template exist. A tenant cannot see or attach another tenant's private bank template.

StatusErrorCondition
401UnauthorizedNot a TENANT_ADMIN.
500The underlying error messageQuery failure. This route returns the thrown message rather than a generic string.
  • Gold rates — auto-sync, and which rate a valuation is priced at.
  • Banks and branches — partner banks and the managers reports go to.
  • ValuationsrateBasis, and the snapshots a valuation keeps.