NewConnect Claude, Gemini, ChatGPT, and other AI agents to API Ninjas via our MCP server →

How to Get Cryptocurrency Prices in Python

Crypto Price APIUpdated October 7, 2026
Create a free API key and it will be pre-filled into every code sample on this page. No credit card required.
Getting a cryptocurrency price in Python takes one GET request to the Crypto Price API with a trading-pair symbol like BTCUSD. This guide walks through the request, the one quirk in the response (prices are strings), and the loop patterns for tracking several coins at once.

Prerequisites

You need three things:

  • Python 3.8 or newer.
  • The requests library.
  • A free API Ninjas API key — issued instantly, no credit card. If you already have an account, it's on your profile page.
shell
pip install requests

Step 1: Fetch your first price

The endpoint is /v1/cryptoprice and takes one parameter: symbol, the trading pair written without a separator — BTCUSD for Bitcoin in US dollars, ETHBTC for Ether priced in Bitcoin. Note that BTCUSDT is Bitcoin priced in Tether, a dollar stablecoin that usually trades close to, but not exactly at, $1 — use the USD symbols whenever you display dollar amounts.

crypto.py
import requests response = requests.get( 'https://api.api-ninjas.com/v1/cryptoprice', params={'symbol': 'BTCUSD'}, headers={'X-Api-Key': 'YOUR_API_KEY'}, timeout=10, ) response.raise_for_status() data = response.json() print(f"BTC: ${float(data['price']):,.2f}") # BTC: $67,214.72

The request on the wire and the full response:

api.api-ninjas.com
GET/v1/cryptoprice?symbol=BTCUSD
200 OKapplication/json
{ "symbol": "BTCUSD", "price": "67214.72364300", "timestamp": 1717603200 }

Step 2: Read the response

Three fields: the symbol you asked for, the price, and a Unix timestamp of the quote. The one thing that trips people up is that price is a string — the API preserves the full exchange precision rather than rounding it through a float. Cast it deliberately:

precision.py
from decimal import Decimal price = Decimal(data['price']) # exact: Decimal('67214.72364300') as_float = float(data['price']) # fine for display: 67214.723643

Use float() for display and charts; use Decimal anywhere the numbers feed accounting, so a satoshi never disappears to floating-point rounding.

Step 3: Harden the client

Two changes make the quick version production-ready: the key moves to an environment variable so it never lands in git, and a shared Session reuses the TLS connection so repeated calls stay fast:

crypto_client.py
import os import requests API_URL = 'https://api.api-ninjas.com/v1/cryptoprice' session = requests.Session() session.headers['X-Api-Key'] = os.environ.get('API_NINJAS_KEY', 'YOUR_API_KEY') def get_crypto_price(symbol: str) -> dict: """Return the latest price for one symbol, e.g. BTCUSD.""" response = session.get(API_URL, params={'symbol': symbol}, timeout=10) response.raise_for_status() return response.json() btc = get_crypto_price('BTCUSD') print(f"BTC: ${float(btc['price']):,.2f}")

Run export API_NINJAS_KEY=your_key once and the script never needs editing. get_crypto_price() is the building block for the next step.

Step 4: Track multiple pairs

A portfolio view is a loop over symbols:

portfolio.py
symbols = ['BTCUSD', 'ETHUSD', 'SOLUSD', 'DOGEUSD', 'XRPUSD'] for symbol in symbols: data = get_crypto_price(symbol) print(f"{symbol:10} ${float(data['price']):>12,.4f}")

For a simple live ticker, poll on a fixed interval:

ticker.py
import time while True: data = get_crypto_price('BTCUSD') print(f"BTC: ${float(data['price']):,.2f}") time.sleep(60) # paid plans: live prices; one poll a minute stays well inside rate limits

On a paid plan, one request a minute is far inside the rate limits and plenty fresh for a dashboard; on the free plan prices update hourly, so poll hourly. If you need tick-by-tick data, that's WebSocket territory — polling HTTP faster buys you 429s, not better data.

Step 5: Handle errors

Three failures cover almost everything: a malformed symbol (400), a missing key (401), and an over-eager polling loop (429):

errors.py
from requests.exceptions import HTTPError, RequestException try: data = get_crypto_price('BTCUSD') except HTTPError as error: status = error.response.status_code if status == 400: print('Invalid symbol — check the format (e.g. BTCUSD, ETHBTC)') elif status == 401: print('Missing or invalid API key') elif status == 429: print('Rate limit exceeded — slow the polling loop down') else: print(f'HTTP {status}: {error.response.text}') except RequestException as error: print(f'Network error: {error}')

Where to go from here

The Crypto Symbols API lists every supported trading pair, the Bitcoin API adds Bitcoin-specific stats, and for fiat pairs the Exchange Rate API uses the same request pattern. The complete script is below.

crypto_prices.py

The complete script from this guide: session reuse, ticker lookup, and a command-line symbol list.

Frequently asked questions

What symbol format does the API use?

Pairs without a separator: BTCUSD for Bitcoin in US dollars, BTCUSDT for Bitcoin in Tether (a stablecoin, not dollars), ETHBTC for Ether in Bitcoin. Premium plans can also quote fiat currencies such as BTCEUR. The Crypto Symbols API lists every supported pair.

How frequently are crypto prices updated?

On paid plans the price is live, read from the exchange at the moment of your request. On the free plan it updates once an hour (the close of the last completed hour). The timestamp field tells you which moment the price is for.

Why is the price returned as a string?

To preserve full precision. Cast to float for display, or use Decimal for financial calculations where rounding errors matter.

Can I get historical crypto prices?

Yes, on premium plans: /v1/cryptopricehistorical returns OHLCV candles for any pair (e.g. BTCUSD, ETHBTC) at intervals from one minute to one day, and the Bitcoin API has a Bitcoin-only history endpoint.

How often should I poll?

On paid plans, once a minute is plenty for a price display and stays well inside rate limits. On the free plan the price changes once an hour, so polling more often returns the same value. For sub-second trading data you want an exchange WebSocket, not HTTP polling.