Error Handling
The Go SDK returns typed errors that you inspect with the standard library's errors.Is and errors.As. Each API failure maps to a specific error type carrying request details for troubleshooting, and each type matches a sentinel value for quick classification.
The Error Interface
Every SDK error (except a couple of primitive ones noted below) implements marketdata.Error:
type Error interface {
error
Unwrap() error
// Retryable reports whether the operation is safe to retry.
Retryable() bool
// SupportInfo formats the request details for a support ticket.
SupportInfo() string
}
Most typed errors embed a SupportContext with the fields that identify the failed request:
type SupportContext struct {
RequestID string // the cf-ray response header
RequestURL string // the full request URL
StatusCode int // the HTTP status code
Timestamp time.Time // when the error occurred (US/Eastern)
Message string // the error description
ExceptionType string // the error type name
}
SupportContext.SupportInfo() returns a ready-to-paste block for Market Data support tickets.
Error Types
| HTTP status | Error type | Sentinel | Retried by SDK |
|---|---|---|---|
| 400 | BadRequestError | ErrBadRequest | no |
| 401 | AuthenticationError | ErrAuthentication | no |
| 402 | PaymentRequiredError | ErrPaymentRequired | no |
| 403 | ForbiddenError | ErrForbidden | no |
| 404 | NotFoundError | ErrNotFound | no |
| 413 | PayloadTooLargeError | ErrPayloadTooLarge | no |
| 429 | RateLimitError | ErrRateLimited | no |
| 500 | InternalError | ErrInternal | no |
| 501–599 | ServerError | ErrServer | yes (exponential backoff) |
| — (connection/timeout) | NetworkError | — | yes |
| — (client-side validation) | ValidationError | ErrInvalidRequest | no |
| — (body cannot be decoded) | ParseError | — | no |
All of these types are exported from the marketdata package (they are aliases for the SDK's internal error types), so you refer to them as marketdata.RateLimitError, marketdata.AuthenticationError, and so on.
A 404 that means "no data for this valid request" is not returned as an error. It is surfaced through the NoData field of the *marketdata.Response. A NotFoundError is only returned for a genuinely missing resource.
Types with extra fields
Some error types carry additional context beyond SupportContext:
RateLimitError—Limit,Remaining,ResetAt, and aWaitDuration()method returning how long to wait before retrying.ForbiddenError—AuthorizedIP,BlockedIP, andTroubleshootingGuide(a 403 usually means your IP changed and access was temporarily blocked).ValidationError—FieldandMessageidentify the offending input. Because no request was made, itsSupportInfo()returns an empty string.
Primitive errors
APIError— returned when the HTTP status was successful but the response body reported an error (for example a status field other than"ok"). It carries only aMessage.
Classifying Errors
Use errors.As when you need a typed error's fields, and errors.Is when you only need to know the category.
package main
import (
"context"
"errors"
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient()
if err != nil {
log.Fatal(err)
}
defer client.Close()
quote, _, err := client.Stocks.Quote(context.Background(), "AAPL")
if err != nil {
handleError(err)
return
}
fmt.Printf("AAPL last: $%.2f\n", quote.Last)
}
func handleError(err error) {
var rateErr *marketdata.RateLimitError
var authErr *marketdata.AuthenticationError
var forbiddenErr *marketdata.ForbiddenError
switch {
case errors.As(err, &rateErr):
fmt.Printf("rate limited; wait %s before retrying\n", rateErr.WaitDuration())
case errors.As(err, &authErr):
fmt.Println("authentication failed. Support info:")
fmt.Println(authErr.SupportInfo())
case errors.As(err, &forbiddenErr):
fmt.Printf("IP %s is blocked; the authorized IP is %s\n",
forbiddenErr.BlockedIP, forbiddenErr.AuthorizedIP)
case errors.Is(err, marketdata.ErrBadRequest):
fmt.Println("bad request:", err)
default:
fmt.Println("request failed:", err)
}
}
Sentinels with errors.Is
When you only need to know the category, match the sentinel directly:
if errors.Is(err, marketdata.ErrNotFound) {
// the requested resource does not exist
}
if errors.Is(err, marketdata.ErrPaymentRequired) {
// your plan does not include this data — upgrade required
}
The full set of sentinels: ErrBadRequest, ErrAuthentication, ErrPaymentRequired, ErrForbidden, ErrNotFound, ErrPayloadTooLarge, ErrRateLimited, ErrInternal, ErrServer, and ErrInvalidRequest.
Deciding Whether to Retry
The SDK already retries transient failures (ServerError and NetworkError) automatically before returning, so a returned error usually means retrying will not help. If you implement your own retry policy, ask the error whether it is safe to retry:
var sdkErr marketdata.Error
if errors.As(err, &sdkErr) && sdkErr.Retryable() {
// transient — safe to retry after a backoff
}
Support Tickets
Any error that embeds SupportContext can produce a support block. Include it when you open a ticket so the team can trace the exact request:
var apiErr *marketdata.InternalError
if errors.As(err, &apiErr) {
fmt.Println(apiErr.SupportInfo())
}
--- MARKET DATA SUPPORT INFO ---
request_id: 8a1b2c3d4e5f6a7b
request_url: https://api.marketdata.app/v1/stocks/quotes/?symbols=AAPL
status_code: 500
timestamp: 2026-07-12 09:30:00
message: internal server error
exception_type: InternalError
--------------------------------