Sync-Convert

API documentation

Base URL https://api.sync-convert.com/v1. All responses are JSON. All timestamps are UTC epoch milliseconds.

Authentication

Pass your API key as a bearer token. Keys come in two modes: sk_live_… bills credits against your quota, sk_test_… returns fixed mock rates and is free.

curl https://api.sync-convert.com/v1/rates/latest?base=EUR \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx"

Keys are stored hashed — we can show you the prefix and last four characters, never the whole key again. Rotate from the dashboard if you lose one.

Response envelope

Every successful response carries provenance so you can decide whether a rate is fresh enough for your use case rather than trusting us blindly.

{
  "success": true,
  "base": "EUR",
  "rates": { "USD": 1.0842, "TRY": 47.3120 },
  "as_of": 1755950412338,
  "age_ms": 412,
  "source_tier": "licensed_ws",
  "stale": false,
  "meta": { "credits_used": 1, "credits_remaining": 29873 }
}

A source_tier of manual means the rate is a fixed figure we maintain by hand for a currency no connected source publishes — currently MAD. It is not live market data. A cross rate touching a manual leg is reported as manual, so you can branch on it if a pegged figure is not acceptable for your use case.

FieldMeaning
as_ofUTC epoch milliseconds of the tick itself
age_msHow long ago that tick was captured
source_tierlicensed_ws · exchange_ws · aggregator · ecb_reference · manual
staletrue when every live source is down and we are serving the last known rate
meta.credits_usedCredits this request consumed
meta.credits_remainingCredits left in the current period

Endpoints

MethodPathDescription
GET/rates/latestLatest rates for a base currency, optionally filtered by symbols.
GET/convertConvert an amount between two currencies.
GET/rates/historical/{date}Rates as of a given date (YYYY-MM-DD).
GET/rates/timeseriesRate series between two dates for one or more symbols.
GET/crypto/latestLatest crypto rates against a fiat base.
GET/currenciesCurrencies currently enabled for your tenant.
GET/usageCredits consumed, quota and period reset time.
GET/healthService status and source health. Used to measure the SLA.

Credit weights

You are billed in call credits, not raw requests, so a cheap lookup costs less than an expensive one. Weights are per plan and visible in your dashboard.

EndpointCredits
GET /rates/latest (single base)1
GET /convert1
GET /rates/latest (over 20 symbols)2
GET /crypto/latest2
GET /rates/historical/{date}3
GET /rates/timeseries (31 days or fewer)5
GET /rates/timeseries (over 31 days)10
WebSocket subscription (per pair, per day)10

Errors

Errors use a consistent shape. Messages are localised via the Accept-Language header (en, fr, ru, tr).

{
  "success": false,
  "error": {
    "code": "CURRENCY_NOT_ENABLED",
    "message": "TRY is not enabled for this tenant.",
    "doc_url": "https://sync-convert.com/docs#errors"
  }
}
HTTPCodeMeaning
401INVALID_API_KEYKey missing, revoked or malformed.
401DEMO_EXPIREDThe 30-day demo key has lapsed.
402PAYMENT_REQUIREDSubscription is past due.
403CURRENCY_NOT_ENABLEDThe currency is not enabled for this tenant.
403PLAN_FEATURE_UNAVAILABLEFeature not included in the current plan, e.g. WebSocket on Starter.
403ORIGIN_NOT_ALLOWEDBrowser request from a domain not on this key's allowlist.
403DOMAIN_LIMIT_REACHEDMore domains requested than the plan's active domain allowance.
429QUOTA_EXCEEDEDMonthly credit allowance is exhausted.
429RATE_LIMITEDToo many requests per second.
503RATES_UNAVAILABLEEvery upstream source is unavailable.

WebSocket

Available on Pro and above. Connect, authenticate with your API key, then subscribe to the pairs you have enabled. Ticks arrive as they land — no polling, and streaming does not consume request credits beyond the per-pair daily subscription charge.

const ws = new WebSocket("wss://stream.sync-convert.com/v1");

ws.onopen = () => {
  ws.send(JSON.stringify({ type: "auth", key: "sk_live_xxxxxxxxxxxx" }));
  ws.send(JSON.stringify({ type: "subscribe", pairs: ["EUR/TRY", "BTC/USD"] }));
};

ws.onmessage = (event) => {
  const tick = JSON.parse(event.data);
  // { pair: "EUR/TRY", rate: 47.3120, as_of: 1755950412338 }
};