UK savings rates, rate history, providers, offers, cards and market data — one key, prepaid in DS Credits.
Guide
Market
Assistant
ds_test_…) are free and need no credits.{ data, meta }.meta.creditsCharged and meta.creditsBalance as you go.curl "https://depositscout.com/api/developer/v1/rates?type=easy-access&provider=chase" \
-H "Authorization: Bearer $DS_API_KEY"Base URL https://depositscout.com/api/developer/v1. Send Authorization: Bearer ds_live_… on every request. Keys are never accepted in the query string or in X-API-Key. A key is shown once when created; revoke it from the portal if it leaks. Live keys can carry an IP allow-list.
ds_test_… keys hit the same endpoints and get the same shape, headers and pagination — but the data is a small sample set of fictional providers, not live rates (meta.sample: true, X-DS-Sample-Data: true). They cost nothing, are rate-limited harder (10/min) and add X-DS-Mode: test. Build against a test key; switch to a live key for real data — your 20 starter credits cover the first real calls.Requests are prepaid in DS Credits: £1 = 10 DS Credits. New accounts start with 20 free credits. Top up from £5 (presets £5, £20, £50, £100). Credits never expire. Each request costs the endpoint's weight below — a page of results is one request. 304 Not Modified, validation errors, 429 and 5xx are free.
/rates, /history, /providers, /switch-offers and everything under them. The other endpoints are marked test keys only in the table below: test keys get sample data as normal, but a live key gets 403 endpoint_restricted and is not charged. Their prices are shown so you can plan; email [email protected] for early access.Responses are shared between callers for up to 60 seconds, so a busy endpoint stays fast; each request is still charged. Cancellation and refunds: see the refund policy.
Counting catches up a moment later. Usage is written a few seconds behind the calls it counts, so a request you made a second ago may not be in /me, the usage page or your balance yet. Nothing is lost — it settles within a few seconds. /me returns usage.asOf so you can tell what the figures are true as of, rather than assuming they are live. Charging itself is immediate: the credits for a request are taken as it is served, and meta.creditsBalance on that response is already up to date.
?limit= (default 25, maximum 50 — larger values are clamped and the applied value is in meta.limit) and ?cursor= from meta.nextCursor.?fields=a,b,c trims each item to the fields you need.ETag. Send it back as If-None-Match and an unchanged result is a free 304 — this is how pollers keep costs down."4.42".60 requests per minute per live key (10 for test keys) and 5,000 per day per account across all keys. Over either limit you get 429 with Retry-After. Every response includes X-RateLimit-Limit, -Remaining and -Reset.
Coming soon — this section describes how webhooks will work so you can plan for them.
Instead of polling, we can call your endpoint when something changes. Webhooks are switched on per account — ask from the Webhooks page of your dashboard.
rate.changed, product.added, product.withdrawn, switch_offer.changed. Filter each endpoint by provider, account type or minimum rate change so you only pay for what you use.{ id, type, createdAt, attempt, data }, where data is the same shape the matching endpoint returns (a history row, a product, an offer).X-DS-Signature: t=<unix seconds>,v1=<hex> where v1 is HMAC-SHA256 of `${t}.${rawBody}` with your endpoint's secret. Reject anything older than five minutes. Also sent: X-DS-Event and X-DS-Delivery.import { createHmac, timingSafeEqual } from "node:crypto";
// Verify before trusting the body. Use the raw request body, not a re-serialised object.
export function verifyDepositScoutSignature(secret, signatureHeader, rawBody) {
const parts = Object.fromEntries(signatureHeader.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(parts.v1, "hex"));
}| Endpoint | Credits | Scope |
|---|---|---|
| GET/ping— Check your key works | Free | any key |
| GET/me— Your account, balance and limits | Free | any key |
| Endpoint | Credits | Scope |
|---|---|---|
| GET/rates— Live UK savings rates | 1 | rates:read |
| GET/rates/{sourceKey}— One savings product | 1 | rates:read |
| Endpoint | Credits | Scope |
|---|---|---|
| GET/history— Rate change history | 2 | history:read |
| GET/history/components— Base and bonus rate history | 2 | history:read |
| GET/rate-trends— Market rate trendsTest keys only | 5 | history:read |
| Endpoint | Credits | Scope |
|---|---|---|
| GET/providers— Provider directory | 1 | providers:read |
| GET/providers/{slug}— One provider | 1 | providers:read |
| GET/providers/{slug}/service-quality— Provider service quality | 2 | providers:read |
| Endpoint | Credits | Scope |
|---|---|---|
| GET/switch-offers— Current account switch offers | 1 | offers:read |
| GET/account-offers— Current account offersTest keys only | 5 | offers:read |
| Endpoint | Credits | Scope |
|---|---|---|
| GET/cards/rewards— Reward credit cardsTest keys only | 5 | cards:read |
| GET/cards/travel— Travel cardsTest keys only | 5 | cards:read |
| GET/cards/travel/fx-history— FX rate historyTest keys only | 5 | cards:read |
| Endpoint | Credits | Scope |
|---|---|---|
| GET/market/overview— Market overviewTest keys only | 5 | market:read |
| GET/market/averages— Average ratesTest keys only | 5 | market:read |
| GET/market/category-stats— Category statisticsTest keys only | 5 | market:read |
| GET/market/card-stats— Card market statisticsTest keys only | 5 | market:read |
| GET/market/hero-stats— Headline statisticsTest keys only | 5 | market:read |
| GET/market/boe-rate— Bank of England base rateTest keys only | 5 | market:read |
| GET/market/ecb-rate— ECB rateTest keys only | 5 | market:read |
| GET/market/inflation— UK inflationTest keys only | 5 | market:read |
| Endpoint | Credits | Scope |
|---|---|---|
| POST/assistant— Ask PennyComing soon | 10 | assistant |
Every error is JSON with error, a plain-English message that says what to do next, requestId and a docs link. None of them cost credits.
| Status | error | When |
|---|---|---|
| 400 | invalid_request | Bad query or path value, such as a malformed cursor. |
| 401 | missing_api_key | No Authorization header, or it isn't a Bearer token. |
| 401 | invalid_api_key | The key isn't one of ours or doesn't exist. |
| 402 | insufficient_credits | Balance is lower than the endpoint's cost. Body has required, balance and topUp. |
| 403 | key_revoked | The key was revoked in the portal. |
| 403 | account_blocked | The account is blocked. The body includes the reason where we can share it. |
| 403 | insufficient_scope | The key doesn't carry the scope the endpoint needs. |
| 403 | ip_not_allowed | The key has an IP allow-list and the caller isn't on it. |
| 403 | licence_reacceptance_required | The Data Licence changed and the grace period has ended. |
| 403 | email_not_verified | Portal only: verify your email before creating a live key or topping up. |
| 403 | webhooks_not_enabled | Portal only: webhooks are an admin-enabled feature. |
| 403 | sample_data_unavailable | A test key was used on an endpoint that has no sample data yet. Use a live key. |
| 403 | endpoint_restricted | A live key was used on an endpoint that is test-only for now. See the open list on the endpoints table. |
| 404 | not_found | Unknown route, unknown sourceKey or slug, or an endpoint that hasn't launched yet. |
| 429 | rate_limited | Burst or daily limit hit. Retry-After tells you when. |
| 500 | internal_error | Our fault. Quote requestId to [email protected]. |
/v1 shapes are frozen: we only add fields. A breaking change ships as /v2 with a six-month overlap and Deprecation / Sunset headers, announced on the changelog. Use of the API is under the Data Licence v2026-09.