Skip to content

Fail fast when Alpha Vantage refuses a request for the account - #63

Merged
thorstenalpers merged 1 commit into
thorstenalpers:mainfrom
werwolfby:fix/alpha-vantage-premium-endpoint
Sep 20, 2026
Merged

thorstenalpers merged 1 commit into
thorstenalpers:mainfrom
werwolfby:fix/alpha-vantage-premium-endpoint

Conversation

@werwolfby

Copy link
Copy Markdown
Contributor

Problem

On a free Alpha Vantage key, GetRecordsAsync gets this reply every time:

{
  "Information": "Thank you for using Alpha Vantage! This is a premium endpoint. You may subscribe to any of the premium plans at https://www.alphavantage.co/premium/ to instantly unlock all premium endpoints"
}

It arrives with HTTP 200, so the parser read it as invalid data ("data is invalid"). The retry policy then ran through the whole back-off budget, even though retrying can never change this answer.

Changes

  • New public FinanceNetAccessDeniedException : FinanceNetException, thrown when the provider refuses the request for the account. Existing catch (FinanceNetException) handlers keep working.
  • AlphaVantageService.ThrowIfRejected checks every response from all four methods. It throws the new exception for a premium-endpoint reply or an invalid/missing API key.
  • PollyPolicyFactory does not retry the new exception, and the service does not wrap it (InnerException is null), the same as FinanceNetNoDataException.
  • The invalid-API-key check used to be only in GetForexRecordsAsync, where it was retried. It now covers all methods and fails at once.
  • The rate-limit reply ("higher API call volume") is still retried, because the per-minute limit clears on its own.
  • GetRecordsAsync still calls TIME_SERIES_DAILY_ADJUSTED, for backward compatibility. On a free key it now fails at once with a clear message instead of retrying.

Tests

  • New AlphaVantageRejectedRequestTests sends the premium reply and the invalid-key reply to each of the four methods and checks that exactly one request is made. It also checks that the rate-limit reply is still retried.
  • Against the old code, all 8 refusal cases fail; with the change they pass.
  • dotnet test --filter TestCategory=Unit: 204 passed.
  • Release build: 0 warnings.

Adding a public exception type calls for a minor version bump in the next release.

Alpha Vantage answers a premium-only endpoint (TIME_SERIES_DAILY_ADJUSTED
on a free key) with HTTP 200 and an "Information" message instead of a
time series. The parser read that as invalid data and the retry policy
ran through the whole back-off budget before failing.

A premium-endpoint refusal or an invalid API key now throws the new
FinanceNetAccessDeniedException from every Alpha Vantage call. Like
FinanceNetNoDataException it derives from FinanceNetException, is not
retried and is not wrapped. The invalid-key check was previously only
in GetForexRecordsAsync and was retried. The rate-limit message stays
retryable.
@thorstenalpers

Copy link
Copy Markdown
Owner

Thanks, great diagnosis. I reproduced it: the same call takes about 6 minutes on main and fails in under a second with your change. Merging. I'll follow up with a small fix for the negative cases in the Alpha Vantage integration tests, they use Assert.ThrowsAsync with the exact base type and need CatchAsync now. Nothing you need to do.

@thorstenalpers
thorstenalpers merged commit 160b488 into thorstenalpers:main Sep 20, 2026
1 check passed
thorstenalpers added a commit that referenced this pull request Sep 20, 2026
* Accept derived exceptions in the Alpha Vantage integration tests

Assert.ThrowsAsync matches the exact type, so the negative cases turned
red once #63 made a refused request throw FinanceNetAccessDeniedException.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Keep an Alpha Vantage rate-limit notice retryable

The notice advertises "premium endpoints", so the "premium endpoint"
marker classified a temporary rate limit as a permanent refusal. The
marker now matches the full sentence. The notice also lacks the old
"higher API call volume" wording, so GetOverviewAsync deserialized it
to an empty object and reported no data without retrying. "rate limit"
is now recognised as a retryable limit in all four methods.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: thorsten <thorsten@PC>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@thorstenalpers thorstenalpers mentioned this pull request Sep 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants