Skip to main content

Official XRates SDKs for PHP, JavaScript, Python, Go and Ruby

· XRates Team

We've published official client libraries for the five languages that ~90% of our traffic hits the API from: PHP, JavaScript / TypeScript, Python, Go and Ruby. Each one is a thin wrapper over the public REST endpoints — no magic, no codegen, no leaky abstractions. You can read the entire source of any of them in one sitting.

Same surface across all five languages, mapped to whatever your runtime considers idiomatic.

Why thin wrappers instead of generated clients

We considered generating clients from the OpenAPI spec (which we publish anyway — feel free to point any generator at it). We didn't, for one reason: generated clients leak. Names like LatestRequest, LatestResponse, RatesResponseRatesValue are the kind of API surface that haunts your codebase for years because the types are deeply nested and exported from the SDK, not your own domain.

Hand-written wrappers stay small. The PHP client is 218 lines. The Go client is under 200. The JS one is 180. You can read and audit the whole thing in five minutes — important when the SDK sits in the hot path of a billing system or a price calculator.

What's in every SDK

The same seven methods, mapped to whatever the language prefers:

Method What it does Endpoint
latest current rates for a base currency GET /api/v1/latest
historical rates on a specific date GET /api/v1/{YYYY-MM-DD}
convert one-shot conversion at the latest (or historical) rate GET /api/v1/convert
timeseries daily rates between two dates GET /api/v1/timeseries
fluctuation min / max / change between two dates GET /api/v1/fluctuation
currencies list of supported currencies GET /api/v1/currencies
status per-source health, mirror of /status GET /api/v1/status

And the same four-tier error hierarchy, so you can catch / errors.As on the cases you care about:

  • ApiError — base. Network failures, malformed responses, unrecognised statuses
  • AuthenticationError — HTTP 401 / 403
  • RateLimitError — HTTP 429
  • ValidationError — HTTP 422, with the raw response body exposed as payload so you can show it to users

If you only catch (ApiError), you'll handle everything. If you want to retry with backoff on 429 specifically, branch on RateLimitError. The hierarchy is the same shape in every language.

PHP

composer require xratesapi/php-sdk
use XRatesApi\Client;

$client = new Client(getenv('XRATES_API_KEY'));

$rates = $client->latest('USD', ['EUR', 'GBP']);
$converted = $client->convert('USD', 'EUR', 100);

PSR-18 compatible: pass any GuzzleHttp\ClientInterface to the constructor to wire in retries, proxies or your own logging middleware. PHP 8.1+, requires guzzlehttp/guzzle ^7.5.

JavaScript / TypeScript

npm install @xratesapi/sdk
import { Client } from '@xratesapi/sdk';

const client = new Client(process.env.XRATES_API_KEY!);

const latest = await client.latest({ base: 'USD', symbols: ['EUR', 'GBP'] });
const conversion = await client.convert('USD', 'EUR', 100);

ESM-only, zero runtime dependencies, native fetch (Node 18+, Deno, Bun, modern browsers). Full TypeScript types. Pass your own fetch via options.fetch if you need polyfilling or a mocked test transport.

Python

pip install xratesapi
from xratesapi import Client

with Client("YOUR_API_KEY") as client:
    rates = client.latest(base="USD", symbols=["EUR", "GBP"])
    converted = client.convert("USD", "EUR", 100)

Built on httpx, supports Python 3.9+. The client is a context manager so the underlying connection pool gets cleaned up automatically. Pass your own httpx.Client if you need transport-level retries, proxies, or async (an async variant ships in a later release).

Go

go get github.com/xratesapi/go-sdk
package main

import (
    "context"
    "fmt"
    "os"

    xratesapi "github.com/xratesapi/go-sdk"
)

func main() {
    client, _ := xratesapi.NewClient(os.Getenv("XRATES_API_KEY"), nil)

    rates, err := client.Latest(context.Background(), xratesapi.RateParams{
        Base:    "USD",
        Symbols: []string{"EUR", "GBP"},
    })
    if err != nil {
        panic(err)
    }
    fmt.Println(rates)
}

Idiomatic Go: context.Context on every call so you control deadlines and cancellation per-request, errors.As for branching on typed errors, zero third-party dependencies (just stdlib net/http). Plug in your own HTTPClient interface for retries.

Ruby

gem install xratesapi
require "xratesapi"

client = XRatesApi::Client.new(ENV["XRATES_API_KEY"])

rates     = client.latest(base: "USD", symbols: %w[EUR GBP])
converted = client.convert("USD", "EUR", 100)

Ruby 2.7+ (3.x recommended), zero third-party dependencies — just stdlib Net::HTTP and JSON. Drops into a Rails initializer in three lines and doesn't pull in Faraday, HTTParty or anything that fights with whatever you already have in your Gemfile.

Error handling, side by side

The same conceptual code, five languages:

PHP

try {
    $client->latest('XXX');
} catch (ValidationException $e) {
    print_r($e->payload());
} catch (RateLimitException $e) {
    // back off and retry
} catch (AuthenticationException $e) {
    // refresh / log in
} catch (ApiException $e) {
    // anything else
}

TypeScript

try {
    await client.latest({ base: 'XXX' });
} catch (err) {
    if (err instanceof ValidationError) console.error(err.payload);
    else if (err instanceof RateLimitError) /* retry */;
    else if (err instanceof AuthenticationError) /* re-auth */;
    else if (err instanceof ApiError) console.error(err.status);
}

Python

try:
    client.latest(base="XXX")
except ValidationError as e:
    print(e.payload)
except RateLimitError:
    pass  # retry
except AuthenticationError:
    pass  # re-auth
except ApiError as e:
    print(e.status)

Go

_, err := client.Latest(ctx, xratesapi.RateParams{Base: "XXX"})

var ve *xratesapi.ValidationError
var rl *xratesapi.RateLimitError
var auth *xratesapi.AuthenticationError
var api *xratesapi.APIError

switch {
case errors.As(err, &ve):   fmt.Println(ve.Payload)
case errors.As(err, &rl):   // retry
case errors.As(err, &auth): // re-auth
case errors.As(err, &api):  fmt.Println(api.Status)
}

Ruby

begin
  client.latest(base: "XXX")
rescue XRatesApi::ValidationError => e
  p e.payload
rescue XRatesApi::RateLimitError
  # retry
rescue XRatesApi::AuthenticationError
  # re-auth
rescue XRatesApi::ApiError => e
  warn e.status
end

This is intentional. Switching languages on the team — or running the same logic in two places (a Node webhook handler and a Python cron job, say) — should not mean re-learning the SDK.

What's next

We're tracking adoption to decide where the sixth SDK goes. The top three candidates are Java / Kotlin (Spring Boot, Android), Rust (for the people emailing us about cargo packages), and C# / .NET (NuGet, Azure Functions). If you want to vote with your stack, drop us a line at [email protected].

In the meantime: all five current SDKs are MIT-licensed, ~200 lines of source each, live under github.com/xratesapi, and ship issues + PRs to the language-specific repo. Pin a version, read the source, ship.

If you haven't yet, grab an API key — the free tier covers 1,000 requests a day, no card required, and works against every method documented above.