Newport Exchange
Developers

Newport Exchange API

A JSON-over-HTTPS REST API for crypto news posts, market listings and daily price history. Authenticated news endpoints and public market-data endpoints share one base URL.

Base URLhttps://api.newportexchange.org/v2Last updated
On this page

Overview

The Newport Exchange API serves the same data that powers Market and News on this site. All endpoints are read-only GET requests that return JSON over HTTPS, and every URL in this reference is relative to https://api.newportexchange.org/v2.

  • Posts (/posts) — curated crypto news with search and filters. Requires an API key.
  • Market data (/crypto/…) — top coins by market capitalisation with price, volume, supply and percentage change. Public.
  • Historical data (/crypto/historical/…, /crypto-history/…) — daily OHLCV candles. Public.
  • News (/news) — the public news feed used by this website. Public.

Timestamps in /posts responses are ISO 8601 in UTC. Public endpoints return the underlying record shapes unchanged, so some use unix seconds instead — each section says which.

Quickstart

1. Make a request without a key. The market-data, historical and news endpoints are public. Fetch Bitcoin’s current listing:

curl -s "https://api.newportexchange.org/v2/crypto/BTC"

You get one JSON object (see The currency object). Note that price_usd is a number but percent_change_day, market_cap_usd and several other numeric fields are strings — see Numbers and timestamps.

2. Use an API key for posts. Request an API key, store it in an environment variable on your server (e.g. NEX_API_KEY), send it in the X-API-Key header and fetch the five latest posts that mention Bitcoin:

curl -s "https://api.newportexchange.org/v2/posts?symbol=BTC&limit=5" \
  -H "X-API-Key: YOUR_API_KEY"

3. Before you ship, read Numbers and timestamps, Rate limits and Handling errors in code — string-encoded numbers, unix-second timestamps and 429 handling are the three things that most often break new integrations.

Endpoints at a glance

Every endpoint is a read-only GET. Paths are shown in full; the Cache-Control value is what the API sends, and also how long the server may reuse a response — polling faster than that returns the same data.

All endpoints
EndpointAccessDescription
GET /v2/PublicGateway name, version and main endpoints. A connectivity check — see Base URL.
GET /v2/postsAPI keyPosts as { data, meta }, newest first by default, paged by page/limit. private, max-age=60.
GET /v2/posts/:slugAPI keyOne post as { data }, body included. private, max-age=60.
GET /v2/crypto/currencies?rankFrom=&rankTo=PublicCoins in a rank window (top 100), bare array, rank ascending. public, max-age=30.
GET /v2/crypto/:symbolPublicOne coin (alias /v2/crypto/currencies/:symbol), bare object. public, max-age=30.
GET /v2/crypto/historical/:symbol?days=PublicMost recent daily candles, newest first. public, max-age=30.
GET /v2/crypto-history/:symbol?days=|from=&to=PublicDaily candles in a time range, oldest first, capped by limit. public, max-age=30.
GET /v2/newsPublicNews articles, bare array, newest first, paged by page/limit. public, max-age=120.
GET /v2/news/:aliasPublicOne article by alias or numeric id, full text. public, max-age=120.

Authentication

API keys are issued by Newport Exchange to registered users. There is no self-serve dashboard yet: email [email protected] to request a key, with your name, organisation and intended use. Keys look like nex_live_… and are shown to you once — store them in a secrets manager.

Send the key in the X-API-Key request header on every call to an authenticated endpoint. Never put it in the query string: URLs end up in proxy logs, browser history and analytics.

Request header
GET /v2/posts HTTP/1.1
Host: api.newportexchange.org
X-API-Key: YOUR_API_KEY

Authentication failures return:

Authentication errors
StatusMessageDescription
401API key requiredThe X-API-Key header is missing or empty.
401Invalid API keyThe key is malformed, unknown or has been revoked.
403API key lacks the required scopeThe key is valid but not permitted to read this resource. Contact us to change its scopes.
429Too many invalid API key attempts — please retry shortlyMore than 30 failed (401) attempts from your IP address within a minute. This 429 carries no RateLimit headers: wait a minute and fix the key before retrying.
Missing key
HTTP/1.1 401 Unauthorized
Content-Type: application/json

{"success":false,"message":"API key required"}
Unknown or revoked key
HTTP/1.1 401 Unauthorized
Content-Type: application/json

{"success":false,"message":"Invalid API key"}

Conventions

Base URL

Every path in this reference is relative to https://api.newportexchange.org/v2; use HTTPS. GET /v2/ returns a small JSON document with the gateway’s name, version and main endpoints — a convenient connectivity check that needs no key.

Response format

  • All responses are JSON (application/json; charset=utf-8), compressed when your client sends Accept-Encoding (most HTTP clients do).
  • /posts wraps results in data (plus meta on lists). The public endpoints return the record itself — a bare array or object, with no envelope.
  • Errors always have success: false and a message — see Errors.
  • Ignore fields you do not recognise: new fields may be added to existing responses — see Ignoring unknown fields.

Numbers and timestamps

  • Some numbers are strings. In the currency object, market_cap_usd, volume_usd_day, price_btc, the supply fields and every percent_change_* field are JSON strings (large integers can exceed JavaScript’s safe integer range). Parse them — Number(x), float(x) or a decimal library — before doing arithmetic. price_usd and all candle prices and volumes are JSON numbers.
  • Prices and volumes are in US dollars unless the field name says otherwise (price_btc, volume_from). Percentages are percent values: "1.64" means +1.64 %, not 164 %.
  • Timestamps. /posts uses ISO 8601 strings in UTC. Candle time and news publishedAt are unix seconds (multiply by 1000 for a JavaScript Date). created_at/updated_at and last_updated are ISO 8601 strings in UTC — see Time conventions.

Parsing numeric strings

Convert the string fields once, right after fetching, and keep missing or malformed values as null — never as 0, which would read as “no change” or “no supply”. Use a decimal type (Decimal in Python, a library such as big.js in JavaScript) when you add up market caps or need exact figures; Number is fine for display and sorting.

JSON types of numeric fields
JSON typeFieldsDescription
numberprice_usd, rank, id; every candle price and volume; candle time; news publishedAtUse as is.
stringprice_btc, volume_usd_day, market_cap_usd, available_supply, total_supply, max_supply, percent_change_hour, percent_change_day, percent_change_weekParse before arithmetic or sorting. As strings, "9" sorts after "10".
string (flag)defi"1" or "0"; compare with === '1'.
nulltotal_supply, max_supply, created_at (currency), urlToImageCan be null; max_supply: null means uncapped.
# Requires jq. "tonumber" converts the string fields; "? // null" keeps
# missing or malformed values as null instead of failing the whole pipeline.
curl -s "https://api.newportexchange.org/v2/crypto/currencies?rankFrom=1&rankTo=100" | jq '
  [ .[] | {
      symbol,
      price_usd,
      market_cap_usd: (.market_cap_usd | tonumber? // null),
      change_24h_pct: (.percent_change_day | tonumber? // null)
    } ]
  | map(select(.change_24h_pct != null))
  | sort_by(-.change_24h_pct) | .[:5]'

Time conventions

Every time in the API is UTC. There are two formats, and mixing up seconds and milliseconds is the most common bug: a unix-seconds value passed straight to JavaScript’s new Date() lands in January 1970.

Time fields and parameters
Field or parameterFormatDescription
time (candle)unix seconds00:00 UTC of the day the candle covers, e.g. 1790899200 = 2026-10-02. Format it in UTC, or the day label shifts by one west of Greenwich.
publishedAt (news)unix secondsPublication time of a public news article.
published_at (posts)ISO 8601, UTC | nulle.g. 2026-09-25T14:03:00.000Z.
created_at, updated_at, last_updatedISO 8601, UTCRecord bookkeeping. last_updated is when CoinMarketCap last changed a quote; updated_at is when Newport Exchange last refreshed the row.
from, to (posts)unix seconds | ISO 8601Inclusive. A date without a time means 00:00 UTC for from and the end of that UTC day for to.
from, to (crypto-history)unix seconds | dateInclusive. A date means 00:00 UTC for both ends.
days (crypto-history)integerA rolling window of N × 24 hours back from the moment of the request — not from your local midnight.
RateLimit-Reset, Retry-Afterseconds (relative)How long to wait, not a timestamp.
# Candle "time" is unix seconds at 00:00 UTC; jq's todate renders it in UTC.
curl -s "https://api.newportexchange.org/v2/crypto-history/BTC?days=3" \
  | jq -r '.[] | "\(.time | todate)  close=\(.close)"'

# Convert one timestamp by hand (GNU date; on macOS use: date -u -r 1790899200)
date -u -d @1790899200 +%F

Coin symbols

  • Symbols are case-insensitive: /crypto/btc and /crypto/BTC return the same coin. The symbol field holds the ticker as listed, e.g. BTC.
  • /crypto/:symbol accepts 1–50 characters of ASCII letters, digits, - and _. The historical endpoints accept 1–15 letters or digits. Anything else returns 400.
  • A few listed coins have tickers outside those rules (for example 币安人生). They appear in ranked lists but cannot be requested by symbol — read them from the list instead.
  • Tickers are not globally unique across the crypto market, and a symbol lookup returns a single coin. Use unique_name (CoinMarketCap’s coin id) when you need a stable identity over time.

Rate limits

Limits are counted in fixed one-minute windows:

  • Per API key on authenticated endpoints (/posts): 60 requests per minute by default, configurable per key — ask when you request one. The budget is shared by the list and single-post endpoints and is counted per key, not per IP, so several servers using one key share it. Requests with a valid key are not subject to the per-IP limit.
  • Per IP address on the public endpoints: 300 requests per minute by default. When exceeded, the message is Too many requests — please slow down.

Responses carry the IETF draft (draft-6) RateLimit header fields so you can pace yourself before you hit the limit. Header names are case-insensitive; the live API sends them in lower case over HTTP/2.

Rate limit response headers
HeaderTypeDescription
RateLimit-PolicystringThe policy in force, e.g. 300;w=60 = 300 requests per 60-second window.
RateLimit-LimitintegerRequests allowed in the current window.
RateLimit-RemainingintegerRequests left in the current window.
RateLimit-ResetintegerSeconds until the window resets.
Retry-AfterintegerOn 429 responses only: seconds to wait before retrying.
Headers on a public endpoint (real response)
HTTP/2 200
ratelimit-policy: 300;w=60
ratelimit-limit: 300
ratelimit-remaining: 297
ratelimit-reset: 19

Over the limit, the API returns 429. Stop sending requests until Retry-After (or RateLimit-Reset) seconds have passed, then resume gradually; if you are throttled again, back off exponentially with jitter — see Retries and backoff. Every request from the same key (or IP) counts against one window, so retrying in a tight loop only produces more 429s.

Response 429
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
RateLimit-Policy: 60;w=60
RateLimit-Limit: 60
RateLimit-Remaining: 0
RateLimit-Reset: 18
Retry-After: 18

{"success":false,"message":"Rate limit exceeded"}

Errors

Errors use standard HTTP status codes and a consistent JSON body: success is always false, message is a human-readable summary, and validation errors add a details array naming each offending field. Match on the status code, not on message text.

Response 400
{
  "success": false,
  "message": "Invalid query parameters",
  "details": [
    { "field": "limit", "issue": "must be an integer between 1 and 100" }
  ]
}
HTTP status codes
StatusTypeDescription
200OKSuccess.
400Bad RequestA parameter is missing, malformed or out of range. Fix the request; do not retry.
401UnauthorizedMissing, unknown or revoked API key.
403ForbiddenThe API key is valid but lacks the scope for this endpoint.
404Not FoundThe resource (post, currency, article) or route does not exist.
429Too Many RequestsRate limit exceeded. Back off until the window resets.
500Internal Server ErrorUnexpected server error. Safe to retry with backoff.
503Service UnavailableA dependency such as the database is temporarily unavailable. Retry with backoff.

Error messages

The messages you can expect, by status. They are meant for logs and humans and may be reworded — branch on the status code.

Error messages by status
StatusMessageDescription
400Invalid query parameters/posts filters; details lists each field and the rule it broke.
400Invalid slug/posts/:slug is not 1–190 characters of A–Z a–z 0–9 . _ -.
400Invalid item format/crypto/:symbol breaks the symbol rules.
400`rankFrom` and `rankTo` must be numbers/crypto/currencies with a non-numeric rank.
400Invalid crypto symbol · Invalid coin symbol: …A historical endpoint received a symbol that is not 1–15 letters or digits.
400A start date (startTime, startDate, from, or days) is required/crypto-history/:symbol without days or from. Related: Start date is not valid, End date is not valid, Start date must be before end date, `days` must be a positive number.
400Invalid news item/news/:alias is not 1–100 characters of A–Z a–z 0–9 . _ -.
401 / 403API key required · Invalid API key · API key lacks the required scopeSee Authentication.
404Post not found · Currency not found · News item not foundThe path is valid but the resource does not exist (or is unpublished).
404No route matches GET …The path itself is not part of the API — check for typos and the /v2 prefix.
429Rate limit exceeded · Too many requests — please slow downPer-key and per-IP limits respectively — see Rate limits.
500 / 503Internal Server Error · Database is temporarily unavailableServer-side; no internal detail is ever included. Retry with backoff.
Response 404 (unknown path)
{"success":false,"message":"No route matches GET /v2/coins"}

Handling errors in code

Route every call through one small wrapper that checks the status, reads message (and details on validation errors) for your logs, and tells the caller whether a retry can help:

  • 400, 401, 403, 404 — permanent for this request. Fix the request, the key or the identifier; do not retry unchanged.
  • 429, 500, 503, network errors and timeouts — transient. Retry with backoff (see Retries and backoff) and keep serving the last good response meanwhile.
  • Show your own wording to end users. message is for developers and may be reworded at any time.
  • A non-JSON body (an HTML error page from a proxy in front of the API) is possible on network-level failures — parse defensively.
# -w appends the status code; branch on it, not on the message text.
curl -s -w '\nHTTP %{http_code}\n' "https://api.newportexchange.org/v2/crypto/NOT-A-COIN"
# {"success":false,"message":"Currency not found"}
# HTTP 404

# -f makes curl exit non-zero (22) on 4xx/5xx, for use in shell scripts.
curl -sf "https://api.newportexchange.org/v2/crypto/BTC" > btc.json || echo "request failed" >&2

Pagination

List endpoints are paginated with page (1-based) and limit. There is deliberately no total count — counting a filtered result set is expensive and goes stale while you page.

  • /posts returns meta.has_more. Request the next page while it is true; stop when it is false.
  • /news returns a bare array and echoes the page in the X-Page and X-Per-Page response headers. Stop when a page comes back with fewer items than limit.

When polling for new content, prefer a time filter over deep page numbers — see Polling for new posts.

Posts

Posts are crypto news articles collected from established publishers, normalised into a clean shape with a plain-text body. Both endpoints require an API key and are sent with Cache-Control: private, max-age=60.

List posts

GET/v2/postsAPI key required

Returns published posts, newest first by default (ties broken by id), as data plus a meta object with the page, limit, has_more and the normalised filters that were applied — from/to are echoed as ISO 8601, and sort and include_body are always present.

curl -s -G "https://api.newportexchange.org/v2/posts" \
  -H "X-API-Key: YOUR_API_KEY" \
  --data-urlencode "q=etf" \
  --data-urlencode "symbol=BTC" \
  --data-urlencode "from=2026-09-01" \
  --data-urlencode "sort=newest" \
  --data-urlencode "limit=20" \
  --data-urlencode "page=1"
Response 200 (illustrative values)
{
  "data": [
    {
      "id": 123,
      "slug": "bitcoin-rallies-as-etf-inflows-accelerate",
      "title": "Bitcoin rallies as ETF inflows accelerate",
      "excerpt": "Spot bitcoin ETFs recorded their strongest week of inflows since...",
      "source": "CoinDesk",
      "image": "https://images.example.com/btc-rally.jpg",
      "source_url": "https://www.coindesk.com/markets/2026/09/25/…",
      "published_at": "2026-09-25T14:03:00.000Z"
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "has_more": true,
    "filters": {
      "q": "etf",
      "symbol": "BTC",
      "from": "2026-09-01T00:00:00.000Z",
      "sort": "newest",
      "include_body": false
    }
  }
}

Filters

All query parameters are optional and can be combined. Unknown parameters are ignored; each parameter may appear only once.

Query parameters for GET /v2/posts
ParameterTypeDefaultDescription
pageinteger 1–100001Page number.
limitinteger 1–10020Posts per page.
qstring ≤ 100 chars—Case-insensitive substring search in the title and body. Matched literally (no wildcards).
symbolstring—Coin ticker, e.g. BTC (1–15 letters or digits, case-insensitive). Returns posts whose title or body contains the ticker as a whole word. It does not match the coin’s name (ETH will not find a story that only says “Ethereum” — combine with q if you need both), and tickers that are also English words (ONE, GAS) match those words too.
sourcestring ≤ 100 chars—Publisher or author name, exact match, case-insensitive, e.g. CoinDesk.
fromunix seconds | ISO 8601—Only posts with published_at at or after this time (inclusive), e.g. 1758808980, 2026-09-01 (midnight UTC) or 2026-09-01T12:00:00Z.
tounix seconds | ISO 8601—Only posts with published_at at or before this time (inclusive). A date without a time means the end of that UTC day, so from=2026-09-01&to=2026-09-01 covers the whole day. Must not be earlier than from.
sortnewest | oldestnewestOrder by published_at.
include_bodytrue | falsefalseInclude the full body of each post. Leave it off for list views to keep responses small.

Invalid values return 400 with a details entry per offending field (see Errors).

Get a single post

GET/v2/posts/:slugAPI key required

Returns one post by its slug, wrapped in data. The body is always included. Unknown or unpublished slugs return 404; a slug that is not 1–190 characters of A–Z a–z 0–9 . _ - returns 400 (Invalid slug).

curl -s "https://api.newportexchange.org/v2/posts/bitcoin-rallies-as-etf-inflows-accelerate" \
  -H "X-API-Key: YOUR_API_KEY"
Response 200 (illustrative values)
{
  "data": {
    "id": 123,
    "slug": "bitcoin-rallies-as-etf-inflows-accelerate",
    "title": "Bitcoin rallies as ETF inflows accelerate",
    "excerpt": "Spot bitcoin ETFs recorded their strongest week of inflows since...",
    "body": "Spot bitcoin ETFs recorded their strongest week of inflows since…\n\nAnalysts attributed the move to…",
    "source": "CoinDesk",
    "image": "https://images.example.com/btc-rally.jpg",
    "source_url": "https://www.coindesk.com/markets/2026/09/25/…",
    "published_at": "2026-09-25T14:03:00.000Z"
  }
}
Response 404
{"success":false,"message":"Post not found"}

The post object

Fields of a post
FieldTypeDescription
idintegerStable numeric identifier.
slugstringURL-safe identifier; use it with /posts/:slug.
titlestringHeadline.
excerptstringPlain-text summary: the start of the body, cut at a word boundary to about 280 characters and ending in "..." when shortened.
bodystringFull article as plain text, paragraphs separated by \n\n, no links or markup. Only with include_body=true on the list endpoint; always on the single-post endpoint.
sourcestring | nullPublisher or author name — the value the source filter matches.
imagestring (URL) | nullLead image URL.
source_urlstring (URL) | nullLink to the original article. Attribute the publisher when you display a post.
published_atstring (ISO 8601, UTC) | nullPublication time, e.g. 2026-09-25T14:03:00.000Z.

Market data

Current listings for the top 100 coins by market capitalisation, sourced from CoinMarketCap. These endpoints are public, rate limited per IP, and need no key. Responses are unwrapped (a bare array or object, no data envelope) and sent with Cache-Control: public, max-age=30, stale-while-revalidate=60.

List currencies by rank

GET/v2/crypto/currencies?rankFrom=1&rankTo=10Public · no key · rate limited per IP

Returns the coins whose current rank is between rankFrom and rankTo inclusive, ordered by rank. Both parameters are required — without them (or with only one) the path is treated as a symbol lookup for currencies and returns 404. Coins that dropped out of the listing are excluded and each rank is returned once, keeping the most recently refreshed coin. (Until that de-duplication, deployed from 2026-10-02, is live, a coin that has just left the top 100 can briefly share its old rank with its replacement — key your own lists by unique_name or symbol, not by rank.)

This rank window is the only pagination: the listing ends at rank 100, so rankFrom=1&rankTo=100 returns everything and a window that reaches past 100 simply returns fewer items. To page, request consecutive windows (1–25, 26–50, …). Rank order can change between refreshes, so a coin can move across windows while you page — fetch the whole top 100 in one request if you need a consistent snapshot.

Query parameters for GET /v2/crypto/currencies
ParameterTypeDefaultDescription
rankFromrequiredinteger ≥ 1—Lowest rank to include (inclusive), e.g. 1.
rankTorequiredinteger ≥ 1—Highest rank to include (inclusive), e.g. 100. If rankFrom is larger, the two are swapped. Non-numeric values return 400.
curl -s "https://api.newportexchange.org/v2/crypto/currencies?rankFrom=1&rankTo=10"
Response 200 for rankFrom=99&rankTo=105 (real, 2026-10-02, trimmed to 5 fields)
[
  {
    "rank": 99,
    "symbol": "SPX",
    "name": "SPX6900",
    "price_usd": 0.44146466228232567,
    "market_cap_usd": "411000550"
  },
  {
    "rank": 100,
    "symbol": "FF",
    "name": "Falcon Finance",
    "price_usd": 0.12276868276089015,
    "market_cap_usd": "410422526"
  }
]

Get a single currency

GET/v2/crypto/:symbolPublic · no key · rate limited per IP

Returns one currency by ticker symbol (case-insensitive; see Coin symbols). /v2/crypto/currencies/:symbol is an equivalent alias. A malformed symbol returns 400 and an unknown one 404. Only active coins are returned: a coin that has just left the top-100 listing is still served for a while with prices from its last refresh, and returns 404 once it is deactivated. Check updated_at — listed coins are refreshed every 30 minutes, so a row last refreshed more than about an hour ago is stale.

curl -s "https://api.newportexchange.org/v2/crypto/BTC"
Response 404
{"success":false,"message":"Currency not found"}

The currency object

Large integers (volume_usd_day, market_cap_usd) are returned as strings because they can exceed JavaScript’s safe integer range. price_btc, supply and percentage fields are also strings; parse them before doing arithmetic. price_usd is a JSON number (a double — round it for display).

Currency object — real GET /v2/crypto/BTC response (2026-10-02)
{
  "id": 12238891,
  "unique_name": "1",
  "name": "Bitcoin",
  "alias": "bitcoin",
  "symbol": "BTC",
  "image": "btc.png",
  "rank": 1,
  "price_usd": 84790.92449061108,
  "price_btc": "1",
  "volume_usd_day": "32833582384",
  "market_cap_usd": "1703635110768",
  "available_supply": "20092187",
  "total_supply": "20092187",
  "max_supply": "21000000",
  "percent_change_hour": "0.0643848",
  "percent_change_day": "1.64656281",
  "percent_change_week": "0.18529061",
  "last_updated": "2026-10-02T00:29:00.000Z",
  "created_at": "2026-03-27T00:30:00.000Z",
  "updated_at": "2026-10-02T00:30:00.000Z",
  "defi": "0"
}
Fields of a currency
FieldTypeDescription
idintegerInternal identifier.
unique_namestringCoinMarketCap’s numeric coin id, as a string.
namestringDisplay name, e.g. Bitcoin.
aliasstringURL slug, e.g. bitcoin.
symbolstringTicker, e.g. BTC.
imagestringIcon file name, e.g. btc.png. A few older rows hold an absolute URL instead.
rankintegerRank by market capitalisation.
price_usdnumberPrice in US dollars.
price_btcstring (decimal)Price in bitcoin.
volume_usd_daystring (integer)24-hour trading volume in US dollars.
market_cap_usdstring (integer)Market capitalisation in US dollars.
available_supplystring (decimal)Circulating supply.
total_supplystring (decimal) | nullTotal supply.
max_supplystring (decimal) | nullMaximum supply; null when uncapped.
percent_change_hourstring (decimal)Price change over 1 hour, in percent.
percent_change_daystring (decimal)Price change over 24 hours, in percent.
percent_change_weekstring (decimal)Price change over 7 days, in percent.
last_updatedstring (ISO 8601)When CoinMarketCap last updated this quote.
created_atstring (ISO 8601) | nullWhen the coin was first recorded; null for some older rows.
updated_atstring (ISO 8601)When Newport Exchange last refreshed the row.
defistring"1" if classified as a DeFi token, otherwise "0".

The internal status and sponsored columns are no longer part of the response (removed 2026-10-02; you may see them until the change reaches production). Do not depend on them.

Historical data

Daily OHLCV (open, high, low, close, volume) candles in US dollars for coins in the top-100 listing. Depth varies by coin: history is being backfilled towards one year per coin and is retained for up to two years, so a recently listed coin may have only a few weeks. The current day’s candle is provisional (see Data freshness). A symbol without candles returns an empty array rather than 404. These endpoints are public, rate limited per IP, and need no key.

  • One candle per coin per UTC day. time is the unix timestamp (seconds) of 00:00 UTC on the day the candle covers.
  • The newest candle is today’s, still in progress. It is rebuilt every 30 minutes from listing snapshots until the day ends, so its high, low, close and volumes change during the day. Exclude it (or label it) if you need only completed days.
  • Gaps are possible. Days with no stored data are simply absent — there is no placeholder candle. Do not assume consecutive items are consecutive days; key by time.

Most recent candles

GET/v2/crypto/historical/:symbol?days=30Public · no key · rate limited per IP

Returns the most recent candles for one coin, newest first. The symbol must be 1–15 letters or digits (400 otherwise).

Query parameters for GET /v2/crypto/historical/:symbol
ParameterTypeDefaultDescription
daysinteger 1–500365Maximum number of candles to return — the most recent N stored days. Values above 500 are capped at 500; a missing, zero or non-numeric value falls back to 365 rather than returning 400.
curl -s "https://api.newportexchange.org/v2/crypto/historical/BTC?days=30"

Candles by date range

GET/v2/crypto-history/:symbol?from=…&to=…Public · no key · rate limited per IP

Returns candles for one coin whose time falls inside a range (both ends inclusive), oldest first. Either days or from is required; a request with neither returns 400.

days=N is a rolling window: from exactly N × 24 hours before the request up to now. Because candles are stamped at 00:00 UTC, that is usually N candles, the last one being today’s in-progress candle. startTime/startDate and endTime/endDate are accepted as aliases of from and to.

Results are capped by limit and, because they are oldest first, a range longer than the cap returns its oldest candles. To read a long range, repeat the request with from set to the last time you received plus 86400.

Query parameters for GET /v2/crypto-history/:symbol
ParameterTypeDefaultDescription
daysinteger ≥ 1—Rolling window of the last N × 24 hours up to now. Takes precedence over from/to. Zero, negative or non-numeric values return 400.
fromunix seconds | date—Range start, e.g. 1756684800 or 2026-09-01 (a date means 00:00 UTC). Required unless days is given.
tounix seconds | datenowRange end. A date means 00:00 UTC, which still includes that day’s candle. Must not be earlier than from.
limitinteger ≥ 1500Maximum candles returned (capped at 500).
curl -s "https://api.newportexchange.org/v2/crypto-history/ETH?from=2026-09-01&to=2026-09-26"
Response 200 for /crypto-history/BTC?days=2 (real, 2026-10-02)
[
  {
    "id": 36705017,
    "coin": "BTC",
    "time": 1790812800,
    "open": 83554.24882704367,
    "close": 84736.36714607803,
    "high": 84874.75752388303,
    "low": 83317.4014195001,
    "volume_from": 391680.8812353876,
    "volume_to": 33189614956.461185,
    "created_at": "2026-10-01T00:00:00.000Z",
    "updated_at": "2026-10-01T23:30:00.000Z"
  },
  {
    "id": 36710899,
    "coin": "BTC",
    "time": 1790899200,
    "open": 84855.65159913615,
    "close": 84790.92449061108,
    "high": 84855.65159913615,
    "low": 84790.92449061108,
    "volume_from": 387229.9138396812,
    "volume_to": 32833582384.886242,
    "created_at": "2026-10-02T00:00:00.000Z",
    "updated_at": "2026-10-02T00:30:00.000Z"
  }
]

The second candle above is the current UTC day captured at 00:30 UTC: one snapshot so far, so open equals high and close equals low. It fills in as the day progresses.

The candle object

Candle object
{
  "id": 36705017,
  "coin": "BTC",
  "time": 1790812800,
  "open": 83554.24882704367,
  "close": 84736.36714607803,
  "high": 84874.75752388303,
  "low": 83317.4014195001,
  "volume_from": 391680.8812353876,
  "volume_to": 33189614956.461185,
  "created_at": "2026-10-01T00:00:00.000Z",
  "updated_at": "2026-10-01T23:30:00.000Z"
}
Fields of a candle
FieldTypeDescription
idintegerInternal identifier.
coinstringTicker symbol, upper case.
timeinteger (unix seconds)Start of the day (00:00 UTC) the candle covers.
opennumberOpening price, USD.
highnumberHighest price, USD.
lownumberLowest price, USD.
closenumberClosing price, USD.
volume_fromnumberVolume traded, in units of the coin.
volume_tonumberVolume traded, in US dollars.
created_atstring (ISO 8601)When the candle was first stored.
updated_atstring (ISO 8601)When the candle was last refreshed.

News (public)

The public feed behind this site’s News page. It is public, rate limited per IP, and needs no key, and returns the stored record shape (unix-second timestamps, camelCase fields). For filtering by coin, source or date and a stable schema, use Posts instead.

List news articles

GET/v2/newsPublic · no key · rate limited per IP

Returns a bare array of articles, newest first, with description shortened to an excerpt of about 280 characters. Sent with Cache-Control: public, max-age=120, stale-while-revalidate=240.

Query parameters for GET /v2/news
ParameterTypeDefaultDescription
pageinteger ≥ 11Page number.
limitinteger ≥ 150Articles per page (capped at 500).
qstring—Case-insensitive substring match on the title and description, matched literally. Only the first 100 characters are used.
langstring—Language code, e.g. en (first 8 characters used).
curl -s -i "https://api.newportexchange.org/v2/news?q=ethereum&limit=10&page=1"
Article object — real list item (2026-10-02), description shortened
{
  "id": 21712,
  "author": "Crypto Potato",
  "title": "Bitcoin Chart Echoes Cycle That Delivered 400% Gains: Analyst",
  "alias": "bitcoin-chart-echoes-cycle-that-delivered-400-gains-analyst",
  "description": "Bitcoin analyst Frank Cappelleri says the OG cryptocurrency’s chart resembles the setup that preceded a 400% rally last cycle...",
  "url": "https://cryptopotato.com/bitcoin-chart-echoes-cycle-that-delivered-400-gains-analyst/",
  "urlToImage": "https://resources.cryptocompare.com/news/27/73372796.jpeg",
  "publishedAt": 1790857884,
  "lang": "en",
  "created_at": "2026-10-01T13:00:00.000Z",
  "updated_at": "2026-10-01T13:00:00.000Z"
}
Fields of a news article
FieldTypeDescription
idintegerNumeric identifier.
authorstringAuthor or publisher; may be empty.
titlestringHeadline.
aliasstringURL slug; use it with /news/:alias.
descriptionstringExcerpt in list responses; full article text from /news/:alias.
urlstring (URL)Original article. Link to it and credit the publisher — see Attributing news sources.
urlToImagestring (URL) | nullLead image.
publishedAtinteger (unix seconds)Publication time.
langstringLanguage code.
created_atstring (ISO 8601)When the article was ingested.
updated_atstring (ISO 8601)When the record last changed.

The internal status and twitter_post columns are no longer part of the response (removed 2026-10-02; you may see them until the change reaches production). Use publishedAt, not created_at, as the article’s date.

Get a news article

GET/v2/news/:aliasPublic · no key · rate limited per IP

Returns one article by alias or numeric id, with the full description. Unknown articles return 404 with News item not found; an identifier that is not 1–100 characters of A–Z a–z 0–9 . _ - returns 400 (Invalid news item).

curl -s "https://api.newportexchange.org/v2/news/bitcoin-chart-echoes-cycle-that-delivered-400-gains-analyst"

Data freshness & sources

Newport Exchange ingests data on a schedule and serves it from its own database. Nothing is streamed live, and there is no WebSocket API. Poll no faster than the data changes:

Data sources and refresh schedule
DatasetTypeDescription
Market listingsCoinMarketCapTop 100 coins by market cap, refreshed every 30 minutes. Responses may also be cached for up to 30 seconds.
Historical candlesCoinMarketCap (CryptoCompare fallback)One candle per coin per UTC day. Today’s candle is built from the 30-minute listing snapshots and changes during the day; yesterday’s is replaced with the provider’s official OHLCV once a day. Older history is backfilled gradually, a few coins per day.
News and postsNewsAPI and CryptoCompare NewsIngested twice a day. Responses may be cached for 1–2 minutes.

Best practices

Keep keys server-side

  • Keep keys in environment variables or a secrets manager, never in source control.
  • Call authenticated endpoints only from your server. If a browser or mobile app needs posts, proxy them through your backend and cache the result.
  • Use one key per application or environment so each can be revoked alone.

Caching

Respect Cache-Control: posts can be reused for 60 seconds, market data for 30 seconds and public news for 2 minutes. Since listings refresh every 30 minutes and news twice a day, caching responses for several minutes in your own layer costs you nothing in freshness and keeps you well inside the rate limits. Treat post id and slug as stable keys for your own storage.

Do not poll any endpoint faster than its max-age: the server caches responses for that long, so a faster poll returns the same data and only spends your rate limit. Older historical candles rarely change — yesterday’s is replaced once with the provider’s official values, and backfill can add missing days — so cache them generously and re-fetch only the most recent few days.

Retries and backoff

  • Retry only 429, 5xx and network errors. Never retry 400, 401 or 404 unchanged — the answer will not change.
  • On 429, wait at least Retry-After seconds (fall back to RateLimit-Reset, then to 60 seconds, when a header is missing) before the next request.
  • Otherwise back off exponentially (1 s, 2 s, 4 s, … capped around 60 s) and add random jitter so many clients do not retry in lockstep. Give up after a few attempts and alert.
  • Watch RateLimit-Remaining and slow down before it reaches zero.

The polling example below includes a complete retry helper.

Timeouts

  • Always set a client timeout — 10 seconds is ample for every endpoint. Neither fetch nor Python requests times out by default.
  • Treat a timeout like a 5xx: retry with backoff, and keep serving the last good response from your cache meanwhile.
  • A 503 means a dependency (usually the database) is briefly unavailable; retry with backoff rather than failing immediately.
  • In interactive apps, also cancel requests whose result is no longer wanted (the user switched coin or left the page) so a slow, older response can never overwrite a newer one.
# --max-time caps the whole request; --retry re-tries timeouts, 408, 429 and
# 5xx with exponential backoff and honours Retry-After (curl 7.66+).
curl -s --connect-timeout 5 --max-time 10 --retry 3 "https://api.newportexchange.org/v2/crypto/BTC"

Browser access (CORS)

Cross-origin requests from browsers are restricted to an allow-list of Newport Exchange origins: a page on any other origin does not receive an Access-Control-Allow-Origin header, so the browser blocks the response. Call the API from your server (or a serverless function) and serve the data to your front end from there — which is also the only safe place for an API key.

Validating input

Check identifiers locally before you put them in a URL. A failed check means “use what you already have” (for example the coin’s row from a ranked list), not “send it and handle the 400”. Prefer symbols, slugs and aliases that came from the API over free-text input, and URL-encode them anyway.

Identifier rules
IdentifierRuleDescription
:symbol1–50 of A–Z a–z 0–9 - _/crypto/:symbol, /crypto/currencies/:symbol. Case-insensitive.
:symbol (history)1–15 of A–Z a–z 0–9/crypto/historical/:symbol, /crypto-history/:symbol and the symbol filter on /posts.
:slug1–190 of A–Z a–z 0–9 . _ -/posts/:slug.
:alias1–100 of A–Z a–z 0–9 . _ -/news/:alias (a numeric id also works).
# Validate before you build the URL; a failed check never costs a request.
SYMBOL="BTC"
if [[ "$SYMBOL" =~ ^[A-Za-z0-9]{1,15}$ ]]; then
  curl -s "https://api.newportexchange.org/v2/crypto-history/$SYMBOL?days=30"
else
  echo "Not requestable by symbol: $SYMBOL (use the ranked list instead)" >&2
fi

Ignoring unknown fields

Within /v2, fields can be added to any response without notice, and internal columns are removed when found (see the 2026-10-02 changelog). Write clients that:

  • read only the fields documented on this page;
  • accept extra fields — no strict schema validation that rejects unknown keys (Zod’s default strip, not .strict(); Pydantic’s default extra="ignore", not "forbid");
  • do not depend on field order, and treat a documented field that is null as “unknown”.

Attributing news sources

Posts and news articles are third-party journalism. When you display one:

  • show the publisher — source on posts, author on news — and link the headline or a “Read more” to the original article (source_url / url);
  • use the excerpt (excerpt / list description) for discovery, not as a substitute for the article;
  • load the lead image from its own host (image / urlToImage), keep a placeholder for when it is missing or slow, and do not re-host it;
  • show the publication time (published_at / publishedAt), not the ingestion time.

Bulk redistribution of API data is restricted by the Terms of Service.

Polling for new posts

To keep a local copy of posts up to date, store the published_at of the newest post you have processed and ask only for posts from that moment on, oldest first. This makes polling idempotent: running it twice fetches nothing new, and a crash between fetching and saving just replays a few posts.

  • Use from with sort=oldest, so posts that arrive while you page are appended at the end instead of shifting earlier pages.
  • from is inclusive, so de-duplicate by id the posts published in the boundary second.
  • Save the cursor only after the posts have been handled. Poll every few minutes at most — news is ingested twice a day.
# One polling step: everything published at or after a stored cursor
# (unix seconds), oldest first, so pages stay stable while new posts arrive.
SINCE=1758808980   # published_at of the newest post you have already processed

curl -s -G "https://api.newportexchange.org/v2/posts" \
  -H "X-API-Key: YOUR_API_KEY" \
  --data-urlencode "from=$SINCE" \
  --data-urlencode "sort=oldest" \
  --data-urlencode "limit=100" \
  --data-urlencode "page=1"
# Repeat with page=2, 3, … while meta.has_more is true.
# Skip ids you have already processed: `from` is inclusive of the boundary.

Handling has_more

Loop while meta.has_more is true, incrementing page and keeping every other parameter identical. Use limit=100 for bulk syncs to make the fewest requests. Do not infer the end from a short page or compute page counts — there is no total. For very large backfills, split the work into from/to windows (for example one per day) rather than paging hundreds deep.

Versioning

The API version is part of the path: everything here is under /v2, and GET /v2/ reports the gateway version (currently 2.0.0). Within /v2, new endpoints, parameters and response fields can be added, so write clients that ignore fields they do not know. Changes that affect callers are recorded in the changelog below, newest first.

Changelog

2026-10-02

  • Field allow-lists (rolling out): currency objects no longer include the internal status and sponsored columns, and news articles no longer include status and twitter_post. New database columns stay private until they are documented here.
  • One row per rank in /v2/crypto/currencies: a coin that has just left the top 100 no longer briefly shares its old rank with its replacement.
  • /v2/crypto/:symbol returns only active coins, the most recently refreshed row first; a deactivated coin returns 404.
  • Documentation: new Endpoints at a glance, Parsing numeric strings, Time conventions, Handling errors in code, Validating input, Ignoring unknown fields and Attributing news sources sections, plus a timeout and cancellation example.

2026-10-01

Documentation only — no change to API behaviour:

  • Confirmed the production base URL https://api.newportexchange.org/v2.
  • Documented the RateLimit-Policy and Retry-After headers, the error messages, the coin-symbol rules and rank-window pagination.
  • Documented historical-data semantics (UTC-day candles, the in-progress current day, gaps, the oldest-first cap) and that a currency’s created_at can be null.
  • Replaced illustrative market, candle and news examples with real responses.

2026-09-27

  • New key-authenticated GET /v2/posts and GET /v2/posts/:slug with search, coin, source and date filters.
  • API keys (X-API-Key) with per-key rate limits and standard RateLimit-* headers.
  • First public reference for the /v2 market-data, historical and news endpoints.

Support

For API keys, higher rate limits, bug reports or questions, email [email protected]. When reporting a problem, include the request URL (without your key), the time in UTC, the HTTP status and the response body. Never send us your API key — tell us the name or email it was issued under instead.