// reference

API documentation

Use the X-API-Key header as the canonical and preferred credential transport. Clients that cannot set custom headers can use the api_key query parameter as a fallback. Base URL is https://api.bullionapi.dev. Usage increments after API-key and quota checks but before endpoint validation.

Query credentials may appear in browser history, copied URLs, referrers, intermediary infrastructure, and logs. Use the X-API-Key header whenever your client supports custom headers. Query authentication does not enable cross-origin browser fetch; CORS remains separate. If both transports are supplied, they must match; differing values return 401 conflicting_credentials.


// quick start

Quick start

Get live prices in 30 seconds. If you don’t have an API key yet, get one here.

GET/v1/latest
curl -H "X-API-Key: bullion_your_key" \
  "https://api.bullionapi.dev/v1/latest?currency=USD"
// endpoint
GET/v1/latest

Latest prices

Returns the most recent cached gold price in the requested base currency. It does not provide a standalone currency-data feed.

// parameters

Query parameters

NameTypeDescription
currencystringISO 4217 currency code. Defaults to USD. See supported currencies below.
unitstringOptional output mass unit: gram/g, kilogram/kg, or troy_oz/toz. Omit or leave empty for legacy toz; invalid values return 400.
api_keystringFallback credential for clients that cannot set the X-API-Key header. Prefer the header transport whenever possible.
// no-code

Spreadsheet and no-code example

For a client that cannot set custom headers, request the URL below. Treat the URL as a secret and prefer the header transport whenever your client supports it.

text
GET https://api.bullionapi.dev/v1/latest?currency=USD&api_key=bullion_your_key
// response

Response

json
{
  "status": "success",
  "currency": "USD",
  "unit": "toz",
  "metals": { "gold": 4527.86 },
  "currencies": { "USD": 1, "EUR": 1.1724, "GBP": 1.3521 },
  "timestamps": { "metal": "2026-08-24T22:00:00.000Z", "currency": "2026-08-24T21:45:00.000Z" }
}

Cached for 1 hour and refreshed by the scheduled data pipeline. Prices default to troy ounces (toz). Request unit=gram or unit=kilogram for converted values; aliases are g, kg, and troy_oz. Results are rounded to eight decimal places.

USD, EUR, and GBP retain the legacy provider-backed currencies map shown above. For a configured projected base, that legacy map is empty. timestamps.metal is the hourly canonical USD metal observation used in the projected price, while timestamps.currency is the separate daily Bullion API conversion snapshot time. The conversion timestamp is not a provider end-of-day observation. A snapshot is accepted through seven UTC calendar days old and becomes stale when older.

Conversion input attribution: Exchange Rates By UniRateAPI. Internal UniRateAPI observations are used only to derive informational metal prices and are never returned by this endpoint.

// endpoint
GET/v1/carat

Gold karat prices per gram

Returns cached gold prices per gram for common karats. Gold is the only supported metal and grams are the only response unit.

// parameters

Query parameters

NameTypeDescription
currencystringThree-letter ISO 4217 code, case-insensitive. Defaults to USD; the currency must have cached gold data.
dateYYYY-MM-DDOptional exact UTC day. The final cached gold observation in that day is used.
api_keystringFallback credential for clients that cannot set the X-API-Key header. Prefer the header transport whenever possible.
// example

Example

GET/v1/carat
curl -H "X-API-Key: bullion_your_key" \
  "https://api.bullionapi.dev/v1/carat?currency=USD"
// response

Response

json
{
  "status": "success",
  "currency": "USD",
  "unit": "gram",
  "as_of": "2026-08-10T14:30:00.000Z",
  "karats": {
    "24k": 145.57407936,
    "23k": 139.50849272,
    "22k": 133.44290608,
    "21.6k": 131.01667142,
    "21k": 127.37731944,
    "18k": 109.18055952,
    "16k": 97.04938624,
    "14k": 84.91821296,
    "12k": 72.78703968,
    "10k": 60.6558664,
    "9k": 54.59027976,
    "8k": 48.52469312,
    "6k": 36.39351984
  }
}

Without date, the current cached gold row is used. With date, the final observation in that UTC day is used. Values are calculated from the exact karat ratio and rounded to eight decimal places. Malformed currency or date values return invalid_currency or invalid_date; a valid but unseeded currency or day returns no_data. Cache read failures return data_unavailable. Requests never bridge currencies or call upstream providers.

// endpoint
GET/v1/timeseries

Historical prices

Returns daily gold closes across a date range. Up to 365 days per request.

// parameters

Query parameters

NameTypeDescription
start_dateYYYY-MM-DDFirst day to include (inclusive).
end_dateYYYY-MM-DDLast day to include (inclusive). Max 365-day range.
currencystringISO 4217 base currency. Defaults to USD.
unitstringOptional output mass unit: gram/g, kilogram/kg, or troy_oz/toz. Omit or leave empty for legacy toz; invalid values return 400.
api_keystringFallback credential for clients that cannot set the X-API-Key header. Prefer the header transport whenever possible.
// example

Example

GET/v1/timeseries
curl -H "X-API-Key: bullion_your_key" \
  "https://api.bullionapi.dev/v1/timeseries?currency=USD&start_date=2025-07-08&end_date=2026-07-08"
// response

Response

json
{
  "status": "success",
  "currency": "USD",
  "unit": "toz",
  "start_date": "2025-07-08",
  "end_date": "2026-07-08",
  "rates": {
    "2025-07-08": {
      "currencies": { "EUR": 0.9134, "GBP": 0.7783 },
      "date": "2025-07-08",
      "metals": { "gold": 3320.10 }
    },
    "2025-07-09": {
      "currencies": { "EUR": 0.9141, "GBP": 0.7790 },
      "date": "2025-07-09",
      "metals": { "gold": 3342.55 }
    }
  }
}

Only cached dates are included in rates. Scheduled refresh and backfill jobs populate the cache; API requests never call upstream providers. Prices default to troy ounces (toz). Request unit=gram or unit=kilogram for converted values; aliases are g, kg, and troy_oz. Results are rounded to eight decimal places; FX rates and date metadata are unchanged.

// endpoint
GET/v1/change

Gold price change

Returns gold's absolute and percentage change between the final cached observation in two UTC days. The endpoint reads the database cache and never calls an upstream provider.

// parameters

Query parameters

NameTypeDescription
currencystringISO 4217 base currency. Defaults to USD.
start_dateYYYY-MM-DDFirst UTC day to compare. Use together with end_date.
end_dateYYYY-MM-DDLast UTC day to compare. Use together with start_date.
date_typestringRolling UTC window: yesterday (1 day), week (7 days), month (30 days), or year (365 days). Use instead of the explicit date pair.
api_keystringFallback credential for clients that cannot set the X-API-Key header. Prefer the header transport whenever possible.
// example

Example

GET/v1/change
curl -H "X-API-Key: bullion_your_key" \
  "https://api.bullionapi.dev/v1/change?currency=USD&date_type=week"
// response

Response

json
{
  "status": "success",
  "currency": "USD",
  "metal": "gold",
  "start_date": "2026-08-03",
  "end_date": "2026-08-10",
  "start_price": 4500,
  "end_price": 4600,
  "change": 100,
  "change_pct": 2.2222
}

The final observation in each UTC day is selected deterministically. Use either both explicit dates or one date_type; mixing them returns conflicting_params. Absolute change is rounded to eight decimal places and change_pct to four. Invalid dates return invalid_date; reversed or overlong ranges return invalid_range; unsupported date types return invalid_date_type. Missing cached data returns no_change_data; a zero start price returns invalid_change_baseline.

// endpoint
GET/v1/rates/{region}

US, UK, and euro-area macro rates

Returns the latest stored macro observations for us, uk, or eu. Each series keeps its own observation period, so the response does not expose one region-wide as_of value. The euro-area response identifies its geography as euro_area; uk provides Bank Rate and CPI.

// parameters

Path and query parameters

NameTypeDescription
regionpathUse us, uk, or eu for the requested macro region.
api_keystringFallback credential for clients that cannot set the X-API-Key header. Prefer the header transport whenever possible.
// example

Example

GET/v1/rates/us
curl -H "X-API-Key: bullion_your_key" \
  "https://api.bullionapi.dev/v1/rates/us"
GET/v1/rates/eu
curl -H "X-API-Key: bullion_your_key" \
  "https://api.bullionapi.dev/v1/rates/eu"
// response

Response

json
{
  "status": "success",
  "region": "us",
  "policy": {
    "policy_target_lower": {
      "series_id": "policy_target_lower",
      "value": 3.5,
      "unit": "percent",
      "observation_period": "2026-07-30",
      "method": "latest",
      "source_series_id": "EFFR.targetRateFrom",
      "source_id": "ny_fed",
      "attribution": "Federal Reserve Bank of New York",
      "fetched_at": "2026-08-01T06:00:00.000Z"
    },
    "policy_target_upper": {
      "series_id": "policy_target_upper",
      "value": 3.75,
      "unit": "percent",
      "observation_period": "2026-07-30",
      "method": "latest",
      "source_series_id": "EFFR.targetRateTo",
      "source_id": "ny_fed",
      "attribution": "Federal Reserve Bank of New York",
      "fetched_at": "2026-08-01T06:00:00.000Z"
    },
    "effective_federal_funds_rate": {
      "series_id": "effective_federal_funds_rate",
      "value": 3.63,
      "unit": "percent",
      "observation_period": "2026-07-30",
      "method": "latest",
      "source_series_id": "EFFR",
      "source_id": "ny_fed",
      "attribution": "Federal Reserve Bank of New York",
      "fetched_at": "2026-08-01T06:00:00.000Z"
    }
  },
  "yields": {
    "treasury_nominal_yield_10y": {
      "series_id": "treasury_nominal_yield_10y",
      "value": 4.25,
      "unit": "percent",
      "observation_period": "2026-06-30",
      "method": "monthly_average",
      "source_series_id": "BC_10YEAR",
      "source_id": "us_treasury",
      "attribution": "U.S. Department of the Treasury",
      "fetched_at": "2026-08-01T06:00:00.000Z"
    }
  },
  "inflation": {
    "cpi_u_yoy": {
      "series_id": "cpi_u_yoy",
      "value": 2.7,
      "unit": "percent",
      "observation_period": "2026-06-30",
      "method": "latest",
      "source_series_id": "CUUR0000SA0",
      "source_id": "bls",
      "attribution": "U.S. Bureau of Labor Statistics",
      "fetched_at": "2026-08-01T06:00:00.000Z"
    }
  }
}

Every observation includes its stable Bullion API series ID, numeric value, unit, observation period, calculation method, source series ID, source ID, attribution, and fetch time. UK gilt curves include nominal and real zero-coupon spot yields plus RPI-linked implied inflation at 2Y, 5Y, 10Y, and 30Y tenors, using official month-end archive dates with the month_end method. The API reads the database cache; protected cron collection is the only upstream caller. Free, Pro, and Enterprise keys receive the same data contract, with only existing quota limits differing.

Methodology: Treasury nominal and real yields cover nominal 2Y, 5Y, 10Y, and 30Y plus real 5Y, 10Y, and 30Y series; each is an arithmetic average of valid daily observations in complete UTC calendar months. Treasury 5Y, 10Y, and 30Y breakevens are daily nominal-minus-real differences calculated only on common dates, then averaged by month. Empty, missing, and N/A Treasury values are absent rather than treated as zero. The BLS CPI-U NSA series CUUR0000SA0 is exposed as year-over-year percent; when BLS does not publish an annual-percent field, Bullion API calculates the twelve-month change from the same month in the prior year. UK Bank Rate uses the latest valid BoE IUDBEDR observation for each date in the refresh range; UK CPI uses the latest numeric monthly ONS D7G7 annual-rate value, stored at month end. UK curve values are direct BoE spot observations and BoE-derived RPI-linked implied inflation values; source dates are preserved, including final business days. Neither UK curve values nor UK Bank Rate/CPI are averaged.

Euro-area data includes ECB deposit facility, main refinancing, and marginal lending rates; AAA nominal 2Y, 5Y, 10Y, and 30Y spot yields; metadata-verified all-items HICP year-over-year inflation as ecb_hicp_yoy; and Consumer Expectations Survey weighted-median 12-month and five-year inflation expectations. CES values use survey_expectationand are not market-implied. ECB AAA yields are arithmetic averages of valid observations from complete UTC calendar months. HICP uses HICP.M.U2.N.000000.4D0.ANR and the provider validates its geography, adjustment, all-items, unit, and annual-rate metadata before accepting rows.

Sources: Federal Reserve Bank of New York reference rates, U.S. Department of the Treasury Daily Interest Rate XML Feed, Bank of England Database and BoE yield curve archives, plus Office for National Statistics D7G7 CPI series, and U.S. Bureau of Labor Statistics Public Data API, and the European Central Bank Data Portal. BLS disclaimer: BLS.gov cannot vouch for the data or analyses derived from these data after retrieval.

// endpoint
GET/v1/central-bank-reserves

Annual central-bank reserves

Annual central-bank reserve values and gold share for Pro and Enterprise accounts. This endpoint is served from Bullion API’s automated cache.

// parameters

Query parameters

NameTypeDescription
countryISO2Optional case-insensitive country code, such as GB.
startYYYYOptional first observation year to include (inclusive).
endYYYYOptional last observation year to include (inclusive).
// example

Example

GET/v1/central-bank-reserves
curl -H "X-API-Key: bullion_your_key" \
  "https://api.bullionapi.dev/v1/central-bank-reserves?country=GB&start=2020&end=2025"
// response

Response

json
{
  "status": "success",
  "methodology": {
    "gold_reserve_value_usd": "total reserves including gold minus total reserves excluding gold",
    "annual_gold_value_change_usd": "current derived gold reserve value minus the prior calendar year",
    "transformation_notice": "Bullion API derives gold reserve values, shares, and annual value changes.",
    "warning": "Annual value changes combine holdings changes, gold-price movement, and revisions and do not measure tonnes bought or sold."
  },
  "coverage": {
    "countries": 1,
    "start_year": 2020,
    "end_year": 2025,
    "source_updated_at": "2026-07-13",
    "fetched_at": "2026-07-30T07:10:00.000Z"
  },
  "reserves": [{
    "country": "GB",
    "country_iso3": "GBR",
    "country_name": "United Kingdom",
    "as_of_year": 2025,
    "total_reserves_usd": 200000000000,
    "reserves_excluding_gold_usd": 175000000000,
    "gold_reserve_value_usd": 25000000000,
    "gold_share_pct": 12.5,
    "annual_gold_value_change_usd": 10000000000,
    "data_quality": null
  }]
}

Source: World Bank, World Development Indicators. The underlying provider is International Monetary Fund, International Financial Statistics. Annual value changes combine holdings changes, gold-price movement, and revisions and do not measure tonnes bought or sold.

// endpoint
GET/v1/central-bank-gold

Central-bank gold holdings

Monthly central-bank gold holdings, including fortnightly sources for Pro and Enterprise accounts, served from Bullion API’s scheduled cache of official national central-bank feeds.

// parameters

Query parameters

NameTypeDescription
layerreported | estimated | bothUse reported. estimated and both remain accepted for response compatibility.
start / endYYYY-MMPaired inclusive month bounds, up to 120 months. Omit both for the latest twelve months.
countryISO2Optional case-insensitive reported-layer country filter, such as GB.
// example

Example

GET/v1/central-bank-gold
curl -H "X-API-Key: bullion_your_key" \
  "https://api.bullionapi.dev/v1/central-bank-gold?layer=reported&start=2021-01&end=2026-06"
// response

Response

json
{
  "status": "success",
  "query": { "layer": "reported", "start": "2021-01", "end": "2026-06", "country": null },
  "reported": {
    "status": "ready",
    "observations": [{
      "country": "GB", "country_iso3": "GBR", "country_name": "United Kingdom",
      "month": "2026-06", "reference_date": "2026-06-01", "cadence": "monthly",
      "holdings_basis": "reported", "holdings_million_fine_troy_oz": 27.4,
      "holdings_tonnes": 852.24,
      "reported_value_usd_millions": null, "reported_value_change_usd_millions": null, "reported_value_eur_millions": null, "reported_value_pln_millions": null, "reported_value_inr_crores": null, "reported_value_sar_millions": null,
      "net_change_tonnes": 6.22
    }, {
      "country": "SG", "month": "2026-06", "holdings_tonnes": null,
      "reported_value_sgd_millions": 539183.2
    }],
    "latest_country_changes": [{
      "country": "GB", "country_iso3": "GBR", "country_name": "United Kingdom",
      "month": "2026-06", "reference_date": "2026-06-01", "cadence": "monthly",
      "holdings_basis": "reported", "holdings_million_fine_troy_oz": 27.4,
      "holdings_tonnes": 852.24,
      "reported_value_usd_millions": null, "reported_value_change_usd_millions": null, "reported_value_eur_millions": null, "reported_value_pln_millions": null, "reported_value_inr_crores": null, "reported_value_sar_millions": null,
      "net_change_tonnes": 6.22, "direction": "buying"
    }]
  },
  "estimated": {
    "status": "not_requested",
    "observations": []
  }
}

Reported holdings are sourced from Official national central-bank feeds, including the ECB Data Portal, the De Nederlandsche Bank gold holdings page, the Bank of England Database, and the Reserve Bank of Australia Official Reserve Assets release, and the Banco de España monetary-gold dataset. The Bank of Korea’s monthly Official Foreign Reserves release and DNB page provide reported holdings; these datasets report stocks of gold held; month-to-month changes do not measure purchases or sales. The DNB page is a published holdings summary, not a historical time series; its entries use an irregular cadence and never fill missing monthly history. Entries are reported holdings and valuations; changes do not represent purchases. Australia’s reported physical gold holdings have been unchanged since 1997, with lent gold reported separately. The Czech National Bank international reserves structure.CNB holdings are reported reserve gold including deposits and swapped gold, so monthly changes reflect reported holdings, not purchases. The Central Bank of the Republic of China (Taiwan) reserve template reports official gold holdings at each reference date; these figures do not measure gold purchases or sales. The Saudi Central Bank (SAMA) Data Template on International Reserves and Foreign Currency Liquidity reports reserve holdings; these reserve stocks do not represent gold purchased or sold, and its directly reported million-riyal valuation is exposed as reported_value_sar_millionswhile tonnes are converted only from the reported volume. The Bank Negara Malaysia National Summary Data Page reports a fortnightly USD-billion Gold valuation without a published quantity, so tonnes remain unavailable and valuation changes are not purchase or sale claims.

// portfolio
GETPUT/v1/portfolio

Gold portfolio settings

Configure one private gold portfolio and its reporting currency. Both methods accept X-API-Key only; the api_key query parameter is not accepted for portfolio routes.

Existing and new keys start with portfolio access set to None. Grant Read for GET, or Read and write for GET and PUT, from the API keys dashboard. Write always includes read. Portfolio requests keep the rolling per-key and source-IP abuse limits but do not increment the monthly market-data quota.

// settings

Create or change the reporting currency

Send { "reporting_currency": "GBP" } with PUT. The value must be one of the supported currencies below and can change while the portfolio has no transactions.

// example

Read the portfolio

GET/v1/portfolio
curl -H "X-API-Key: bullion_your_key" \
  "https://api.bullionapi.dev/v1/portfolio"
// response

Empty portfolio response

json
{
  "status": "success",
  "portfolio": {
    "id": "d799789a-e1af-4c41-a79a-611e18f0df31",
    "metal": "gold",
    "reporting_currency": "GBP",
    "created_at": "2026-08-14T11:00:00.000Z",
    "updated_at": "2026-08-14T11:00:00.000Z"
  },
  "summary": {
    "gold_quantity_grams": "0.000000000000000",
    "cost_basis": "0.00000000",
    "realised_pnl": "0.00000000",
    "valuation": {
      "status": "unavailable",
      "spot_price": null,
      "current_value": null,
      "unrealised_pnl": null,
      "price_timestamp": null
    }
  }
}

Decimal summary values remain strings, including zero. Valuation comes from the latest stored gold spot price in the reporting currency and is current when that price is at most six hours old, stale when older, and unavailable when no matching-currency price exists — there is no cross-currency fallback and portfolio reads never call an upstream provider. When unavailable, spot_price, current_value, unrealised_pnl, and price_timestamp are null rather than zero; holdings and cost basis always remain available. Missing portfolios return portfolio_not_found, and keys without the required action return insufficient_scope.

// purchase

Record an exact purchase

POST /v1/portfolio/transactions with portfolio write access and an opaque Idempotency-Key header. Quantity accepts up to eight fractional digits in grams, kilograms, or troy ounces and is returned in grams at 15 decimal places. Money is returned as eight-place decimal strings using round-half-up.

bash
curl -X POST "https://api.bullionapi.dev/v1/portfolio/transactions" \
  -H "X-API-Key: bullion_your_key" \
  -H "Idempotency-Key: purchase-2026-08-19-001" \
  -H "content-type: application/json" \
   --data '{"transaction_date":"2026-08-19","quantity":"1.005","weight_unit":"troy_oz","unit_price":"2500.125","delivery_cost":"12.345678945","fees":"1.005","form":"coin","cgt_status":"exempt"}'

A repeated canonical request with the same key replays its original result. A changed request with that key returns 409 idempotency_conflict. Dates must be real calendar dates no later than today; descriptions are optional and limited to 500 characters. Invalid fields return 400 invalid_purchase with field details.

// allocation

List open lots and allocate a sale explicitly

GET /v1/portfolio/lots lists only your open purchase lots with a positive remaining quantity, ordered by purchase date, creation time, then ID. Each lot carries its stable lot_id, originating purchase_transaction_id, date, form, CGT status, remaining quantity, and remaining cost basis.

GET/v1/portfolio/lots
curl -H "X-API-Key: bullion_your_key" \
  "https://api.bullionapi.dev/v1/portfolio/lots"
json
{
  "status": "success",
  "lots": [{
    "lot_id": "22222222-2222-4222-8222-222222222222",
    "purchase_transaction_id": "11111111-1111-4111-8111-111111111111",
    "transaction_date": "2026-08-19",
    "form": "coin",
    "cgt_status": "exempt",
    "remaining_quantity_grams": "31.258994184000000",
    "remaining_cost_basis": "2525.97630395"
  }]
}

A sale defaults to FIFO lot selection. To choose the disposed lots yourself, send allocations on the sale body: an array of { "lot_id", "quantity_grams" } objects whose quantities sum exactly to the sale quantity. Every selected lot must belong to your account, match the sale's form and CGT treatment, predate or equal the sale date, and have enough remaining quantity. Any missing, foreign, duplicated, incompatible, over-allocated, or under-allocated lot fails the whole sale without mutation: eligibility and exact-sum violations return 400 invalid_allocation, lots dated after the sale return 400 sale_date_invalid, and exhausted selections return 409 insufficient_holdings. Committed sales report their allocation_method as fifo or explicit and return the committed allocations.

// history

Browse transaction history

GET /v1/portfolio/transactions returns only the authenticated owner's transactions. Results use the stable order transaction_date DESC, created_at DESC, id DESC. The limit defaults to 50 and is capped at 100. Follow the opaque cursor in next_cursorwith cursor for the next page; do not depend on its internal format.

Filter with type, form, and cgt_status, and restrict the period with either calendar_year=2025 (1 January through 31 December inclusive) or uk_tax_year=2025-26 (6 April through 5 April inclusive; the two years in the label must be consecutive). The two period filters are mutually exclusive. Invalid values return invalid_filter, invalid_calendar_year, invalid_tax_year, or conflicting_periods.

GET/v1/portfolio/transactions?limit=50&uk_tax_year=2025-26
curl -H "X-API-Key: bullion_your_key" \
  "https://api.bullionapi.dev/v1/portfolio/transactions?limit=50&uk_tax_year=2025-26"
json
{
  "status": "success",
  "transactions": [{
    "type": "purchase",
    "version": 1,
    "id": "11111111-1111-4111-8111-111111111111",
    "transaction_date": "2026-08-19",
    "created_at": "2026-08-19T12:00:00.000Z",
    "entered_quantity": "1.00500000",
    "weight_unit": "troy_oz",
    "quantity_grams": "31.258994184000000",
    "unit_price": "2500.12500000",
    "gross_cost": "2512.62562500",
    "delivery_cost": "12.34567895",
    "fees": "1.00500000",
    "total_cost": "2525.97630395",
    "cost_per_gram": "80.80798407",
    "form": "coin",
    "cgt_status": "exempt",
    "description": "One-ounce gold coin",
    "lots": [{
      "id": "22222222-2222-4222-8222-222222222222",
      "open_quantity_grams": "31.258994184000000",
      "open_cost_basis": "2525.97630395"
    }],
    "allocations": [],
    "gross_proceeds": "0.00000000",
    "net_proceeds": "0.00000000",
    "disposed_cost": "0.00000000",
    "realised_pnl": "0.00000000"
  }],
  "next_cursor": null
}

GET /v1/portfolio/transactions/{id} returns the same transaction representation, including its open lot and allocation collections. Decimal quantities and monetary values are always strings. Invalid page, filter, or period parameters return invalid_limit, invalid_cursor, invalid_filter, invalid_calendar_year, invalid_tax_year, or conflicting_periods; missing and cross-account identifiers return transaction_not_found. Every response carries an ETag header with the quoted transaction version.

// corrections

Edit a recorded transaction

PATCH /v1/portfolio/transactions/{id} corrects one owned transaction without corrupting its allocations. Send If-Match: "3" quoting the current version from the ETag header. The body replaces every mutable field — date, quantity, weight unit, unit price, delivery cost, fees, form, CGT status, and description — while type and ownership stay fixed.

bash
curl -X PATCH "https://api.bullionapi.dev/v1/portfolio/transactions/11111111-1111-4111-8111-111111111111" \
  -H "X-API-Key: bullion_your_key" \
  -H 'If-Match: "1"' \
  -H "content-type: application/json" \
   --data '{"transaction_date":"2026-08-19","quantity":"1.005","weight_unit":"troy_oz","unit_price":"2510.5","delivery_cost":"12.34","fees":"1.005","form":"coin","cgt_status":"exempt"}'

A successful edit increments the version, returns the fresh ETag, and recalculates affected values immediately: purchases refresh their lot totals, and sales release and rebuild only their own allocations using the stored FIFO or explicit method before recomputing disposed cost and realised P&L. A purchase edit must leave enough quantity and basis for every sale that allocated it, cannot move after such a sale, and cannot change form or CGT treatment while those allocations exist. A missing or stale precondition returns 412 stale_transaction: reload the latest record and resubmit deliberately. Foreign identifiers return 404 transaction_not_found, and every rejected edit rolls back completely.

// deletion

Delete a transaction

DELETE /v1/portfolio/transactions/{id} removes an owned purchase or sale with portfolio write access and rebalances your holdings. Send the transaction's version as an ETag-style If-Match header ("1"); a missing or mismatched version returns 412 stale_transaction without mutating anything. Deleting a sale restores every allocated quantity and cost basis to its originating purchase lot.

bash
curl -X DELETE "https://api.bullionapi.dev/v1/portfolio/transactions/11111111-1111-4111-8111-111111111111" \
  -H "X-API-Key: bullion_your_key" \
  -H 'If-Match: "1"'
json
{ "status": "success", "deleted_transaction_id": "11111111-1111-4111-8111-111111111111" }

A purchase that has already funded a sale cannot be deleted and returns 409 transaction_in_use; delete the dependent sale first. Missing and cross-account identifiers return transaction_not_found.

// gains

Tax-year gains

GET /v1/portfolio/gains sums realised profit or loss from your sale transactions inside one period and groups it by the CGT status recorded on each sale. Supply exactly one of calendar_year=2025 (1 January through 31 December inclusive) or uk_tax_year=2025-26 (6 April through 5 April inclusive; the two years in the label must be consecutive) — the two filters are mutually exclusive.

bash
curl -H "X-API-Key: bullion_your_key" \
  "https://api.bullionapi.dev/v1/portfolio/gains?uk_tax_year=2025-26"
bash
curl -H "X-API-Key: bullion_your_key" \
  "https://api.bullionapi.dev/v1/portfolio/gains?calendar_year=2025"
json
{
  "status": "success",
  "period": {
    "kind": "uk_tax_year",
    "label": "2025-26",
    "start": "2025-04-06",
    "end": "2026-04-05"
  },
  "buckets": {
    "exempt": "1234.50500000",
    "not_exempt": "99.99400000",
    "unknown": "0.00000000"
  },
  "total": "1334.49900000"
}

The response echoes the resolved period and returns buckets.exempt, buckets.not_exempt, and buckets.unknown plus their total, all as exact decimal strings. Omitting both filters returns missing_period; supplying both returns conflicting_periods; malformed labels return invalid_calendar_year or invalid_tax_year. Figures exclude annual allowances, matching and pooling rules, and tax rates. They are not tax advice.

// gains

Tax-year gains

GET /v1/portfolio/gains sums realised profit or loss from your sale transactions inside one period and groups it by the CGT status recorded on each sale. Supply exactly one of calendar_year=2025 (1 January through 31 December inclusive) or uk_tax_year=2025-26 (6 April through 5 April inclusive; the two years in the label must be consecutive) — the two filters are mutually exclusive.

bash
curl -H "X-API-Key: bullion_your_key" \
  "https://api.bullionapi.dev/v1/portfolio/gains?uk_tax_year=2025-26"
bash
curl -H "X-API-Key: bullion_your_key" \
  "https://api.bullionapi.dev/v1/portfolio/gains?calendar_year=2025"
json
{
  "status": "success",
  "period": {
    "kind": "uk_tax_year",
    "label": "2025-26",
    "start": "2025-04-06",
    "end": "2026-04-05"
  },
  "buckets": {
    "exempt": "1234.50500000",
    "not_exempt": "99.99400000",
    "unknown": "0.00000000"
  },
  "total": "1334.49900000"
}

The response echoes the resolved period and returns buckets.exempt, buckets.not_exempt, and buckets.unknown plus their total, all as exact decimal strings. Omitting both filters returns missing_period; supplying both returns conflicting_periods; malformed labels return invalid_calendar_year or invalid_tax_year. Figures exclude annual allowances, matching and pooling rules, and tax rates. They are not tax advice.

// rate limits

Rate limits

Authenticated successful market-data /v1 responses include these headers so you can track usage in real time. X-API-CURRENT and X-API-QUOTA are additive compatibility aliases; the existing x-ratelimit-* names remain supported. Portfolio responses omit these monthly quota headers because portfolio operations do not use the monthly market-data quota. Each API key also permits 60 requests per rolling 60-second window across REST and hosted MCP; short-window denials return rate_limitedand do not consume monthly quota.

HeaderTypeDescription
x-ratelimit-limitintegerYour plan’s monthly cap.
x-ratelimit-usedintegerRequests used this month.
x-ratelimit-remainingintegerRequests remaining.
x-ratelimit-resetISO 8601When the quota resets (1st 00:00 UTC).
X-API-CURRENTintegerCompatibility alias for x-ratelimit-used.
X-API-QUOTAintegerCompatibility alias for x-ratelimit-limit.
// errors

Error responses

This is a breaking change: every application-authored JSON HTTP error now uses one nested envelope. Clients must stop reading legacy top-level error, code, and quota fields. The HTTP status remains the source of truth. Better Auth, framework-owned responses, and MCP JSON-RPC errors keep their protocol contracts.

401 — Missing API key

http
HTTP/1.1 401 Unauthorized
content-type: application/json

{
  "status": "error",
  "error": {
    "code": "missing_api_key",
    "message": "Missing API key"
  }
}

401 — Conflicting credentials

http
HTTP/1.1 401 Unauthorized
content-type: application/json

{
  "status": "error",
  "error": {
    "code": "conflicting_credentials",
    "message": "Conflicting API key credentials"
  }
}

429 — Monthly quota exceeded

http
HTTP/1.1 429 Too Many Requests
x-ratelimit-limit: 30
x-ratelimit-used: 30
x-ratelimit-remaining: 0
x-ratelimit-reset: 2026-08-01T00:00:00.000Z
X-API-CURRENT: 30
X-API-QUOTA: 30
retry-after: 86400

{
  "status": "error",
  "error": {
    "code": "quota_exceeded",
    "message": "Your monthly usage limit has been reached. Please upgrade your subscription plan.",
    "plan": "free",
    "used": 30,
    "limit": 30,
    "reset_at": "2026-08-01T00:00:00.000Z"
  }
}

429 — Rolling API-key limit

http
HTTP/1.1 429 Too Many Requests
retry-after: 42

{
  "status": "error",
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded"
  }
}

400 — Bad request

http
HTTP/1.1 400 Bad Request
content-type: application/json

{
  "status": "error",
  "error": {
    "code": "invalid_range",
    "message": "The requested range is invalid."
  }
}

400 — Invalid unit

http
HTTP/1.1 400 Bad Request
content-type: application/json

{
  "status": "error",
  "error": {
    "code": "invalid_unit",
    "message": "The requested output unit is invalid."
  }
}

Registered error codes

CodeStatusDescription
config_error500The API is not configured correctly.
billing_state_conflict503The billing state needs support attention.
annual_billing_unavailable503Annual billing is currently unavailable.
billing_interval_change_conflict409A billing interval change conflicts with the current billing state.
billing_interval_change_expired409The billing interval change preview has expired.
billing_interval_change_failed402The billing interval change payment failed.
billing_interval_change_invalid400The billing interval change is invalid.
billing_interval_change_origin_forbidden403This billing action requires a trusted origin.
billing_interval_change_revalidation_conflict409The subscription could not be verified for an annual-to-monthly change.
billing_interval_change_processing409The billing interval change is already processing.
billing_cancellation_conflict409The subscription cancellation conflicts with the current billing state.
billing_cancellation_failed503The subscription cancellation could not be applied.
billing_cancellation_invalid400The subscription cancellation is invalid.
conflicting_credentials401The supplied API-key credentials do not match.
data_unavailable503The requested data is not available.
email_not_verified403The API-key owner must verify their email address.
invalid_api_key401The API key is invalid or revoked.
invalid_country400The country code is invalid.
invalid_currency400The currency code is invalid.
invalid_date400The date format is invalid.
invalid_layer400The central-bank gold layer is invalid.
invalid_range400The requested range is invalid.
invalid_region400The macro region is invalid.
invalid_unit400The requested output unit is invalid.
invalid_year400The year format is invalid.
method_not_allowed405The API does not support this method.
missing_api_key401An API key is required.
missing_params400Required request parameters are missing.
no_data404Cached data is not available.
plan_required403The request requires an eligible subscription plan.
quota_exceeded429The monthly request quota has been exceeded.
rate_limited429The API key has exceeded its rolling request window.
unauthenticated401A signed-in session is required.
api_key_not_found404The API key was not found.
insufficient_scope403API key lacks the required portfolio scope
invalid_portfolio_permission400The portfolio permission is invalid.
portfolio_not_found404The account does not have a portfolio.
portfolio_currency_locked409The reporting currency is locked after the first purchase.
missing_idempotency_key400An Idempotency-Key header is required for portfolio mutations.
invalid_purchase400The purchase request is invalid
invalid_sale400The sale request is invalid
invalid_allocation400The selected sale lots are invalid.
idempotency_conflict409The Idempotency-Key was already used with another request.
invalid_cursor400The transaction history cursor is invalid.
invalid_limit400The transaction history page size is invalid.
invalid_filter400The transaction history filter value is invalid.
invalid_calendar_year400The transaction history calendar year is invalid.
invalid_tax_year400The UK tax-year label is invalid.
missing_period400Use either calendar_year or uk_tax_year.
conflicting_periods400Use either calendar_year or uk_tax_year, not both.
transaction_not_found404The transaction was not found.
stale_transaction412Load the transaction again and retry with its latest ETag.
transaction_in_use409Gold from this purchase has been sold and cannot be deleted.
sale_date_invalid400The sale date is invalid.
insufficient_holdings409The portfolio does not have enough gold for this sale.
conflicting_params400The request parameters conflict.
invalid_date_type400The date type is invalid.
no_change_data404Cached gold data is missing for one or both requested dates.
invalid_change_baseline422The starting gold price must be non-zero.
health_degraded503The database is unreachable or cached data is stale.
unknown_metal400The requested metal is invalid.
internal_error500The daily image data could not be loaded.
render_error500The daily image could not be rendered.
invalid_json400Invalid JSON
invalid_email400Invalid email
invalid_request400Invalid one-click unsubscribe request
invalid_token400Invalid or expired unsubscribe token
expired_token400Invalid or expired unsubscribe token
// currencies

Supported currencies

Reviewed currencies in this deployment’s static configuration: USD, EUR, GBP. Static documentation does not check materialised price rows. Authenticated GET /v1/symbols is authoritative for currently serviceable runtime currency choices. USD is the default.

Currency conversion input attribution: Exchange Rates By UniRateAPI. These are Bullion API conversion snapshots, not provider end-of-day rates.

// mcp server

Use from Claude, ChatGPT, Cursor

Bullion hosts an MCP server at https://mcp.bullionapi.dev. AI clients call the get_gold_latest tool directly — same API key, same quota.

// one-click install

Install in Cursor · Install in VS Code

// manual install

Claude Desktop, ChatGPT (Settings → Connectors) and other clients: add the server with your API key as a Bearer token.

json
{
  "mcpServers": {
    "bullion": {
      "type": "http",
      "url": "https://mcp.bullionapi.dev",
      "headers": { "Authorization": "Bearer bullion_your_key" }
    }
  }
}
// terminal CLI

Use the CLI

For people working in a terminal, @paulund/bullion-cli provides a focused command-line view of the cached gold price data.

bash
npm install --global @paulund/bullion-cli
export BULLION_API_KEY=your_key_here
bullion --asset gold
npx @paulund/bullion-cli --asset gold --currency EUR

This is the terminal-user path. For AI agents, see the MCP server documentation.

// machine-readable

OpenAPI spec

The full API contract as OpenAPI 3.1, for code-gen, Postman and AI function-calling clients: api.bullionapi.dev/openapi.json.