Skip to main content

SDK Requirements

  • Last updated
  • View as Markdown

This document defines the requirements for official Market Data SDKs. Use this as acceptance criteria when building or evaluating SDK implementations.

Core Principles​

Language-Idiomatic Design​

SDKs must feel native to their language. We adapt to the language's conventions—the language does not adapt to us.

This means:

  • Use the language's standard patterns for errors, async, and data structures
  • Follow the language's naming conventions (snake_case vs camelCase vs PascalCase)
  • Return data types that developers in that ecosystem expect
  • Don't force patterns from one language onto another
  • Follow official style guides and best practices for each language

Easy Default Requests​

Customers must be able to make a request and inspect the result in 2–3 lines of code. Every endpoint must have a simple, zero-configuration path (beyond authentication) that returns a useful, printable result.

This means:

  • Every endpoint method must work with just required parameters (e.g., a symbol) — no mandatory configuration or boilerplate
  • Response objects must support string conversion that produces a readable summary (see §11.6)
  • Environment-based token loading (MARKETDATA_TOKEN) must work automatically so users can skip explicit auth setup
  • The combination of env-based auth + simple method call + print/log must produce a meaningful result

Examples​

Python (3 lines):

from marketdata import MarketDataClient
client = MarketDataClient()
print(client.stocks.quotes("AAPL"))

Go (3 lines, excluding package/import):

client, _ := marketdata.NewClient()
quotes, _ := client.Stocks.Quotes("AAPL")
fmt.Println(quotes)

PHP (3 lines):

$client = new MarketDataClient();
$quote = $client->stocks->quotes("AAPL");
echo $quote;

Required Style Guides​

LanguageStyle Guide / Best Practices
PythonPEP 8, PEP 257 (docstrings)
PHPPSR-12, PSR-4 (autoloading)
JavaScriptMDN Guidelines
TypeScriptTypeScript Handbook
GoEffective Go, Go Code Review Comments
JavaGoogle Java Style Guide
C#.NET Framework Design Guidelines
RustRust API Guidelines

SDKs must pass the standard linter for their language (e.g., ruff for Python, phpcs for PHP, golint for Go).

LanguageError HandlingAsync PatternDefault OutputNaming
PythonExceptions*Sync (async optional)pandas DataFramesnake_case
JavaScriptPromise rejectionasync/awaitPlain objectscamelCase
PHPExceptionsSyncObjects/arrayscamelCase
GoReturn errorBlocking + contextStructsPascalCase exported
JavaExceptionsCompletableFuturePOJOscamelCase
C#ExceptionsTask async/awaitObjectsPascalCase
RustReturn Result<T, E>async with runtimeStructssnake_case

* Python SDK Technical Debt: The current Python SDK (v1.x) returns MarketDataClientErrorResult instead of raising exceptions. This is non-idiomatic and will be fixed in v2.0. New SDKs must use idiomatic patterns from day one.


Quick Reference​

RequirementPriority
Easy default requests (2–3 lines)Must
Bearer token authenticationMust
Environment-based configurationMust
Connection poolingMust
Fixed 99-second timeoutMust
Typed input validationMust
Typed output modelsMust
Decimal type for monetary valuesMust
Structured error handlingMust
API credit trackingMust
Retry with exponential backoffMust
API status awarenessShould
Multiple output formatsShould
Batch/concurrency helpersShould
Ecosystem-native advanced formatsOptional

1. Client Architecture​

1.1 Client Object​

The SDK must expose a top-level client object (e.g., MarketDataClient) that:

  • Accepts an API token via constructor parameter OR reads from environment variable MARKETDATA_TOKEN
  • Allows base URL override (default: https://api.marketdata.app)
  • Allows API version override (default: v1)
  • Uses a single HTTP client instance with connection pooling
  • Provides explicit cleanup/close method for releasing resources
  • Exposes current rate limit state
  • Sets User-Agent header: marketdata-sdk-{language}/{version} (e.g., marketdata-sdk-php/1.0.0)
  • Fixed HTTP timeout: 99 seconds

1.2 Resource Groupings​

The client must expose these resource namespaces:

client.stocks
client.options
client.funds
client.markets
client.utilities

2. Required API Coverage (Canonical Docs Are Source of Truth)​

Do not duplicate REST paths, payload schemas, or parameter contracts in this document.

  • Canonical REST documentation is authoritative for endpoint paths, request parameters, response schemas, and behavior.
  • If this SDK requirements doc conflicts with REST API docs, the REST API docs win.
  • SDK acceptance should verify method coverage against canonical docs, not against hardcoded paths here.

2.1 Canonical API Documentation​

2.2 Required SDK Method Coverage​

SDKs must provide first-class methods for the following capabilities (language-idiomatic naming is expected):

ResourceRequired SDK Methods (capabilities)
stocksprices, quotes, candles, earnings, news
optionschain, expirations, quotes, lookup
fundscandles
marketsstatus
utilitiesstatus, headers, user

3. Universal Parameters​

All endpoint methods must support these parameters:

ParameterValuesDescription
dateformattimestamp, unix, spreadsheetControls date output format
columnsarray of stringsFilter response to specific columns
headersbooleanInclude headers in CSV output
humanbooleanUse human-readable field names

4. Configuration Cascade​

All SDKs must implement a three-tier configuration system where more specific settings override less specific ones:

Priority Order (Lowest → Highest)​

PrioritySourceScopeExample
1 (lowest).env fileProject-level.env file in project root
2Environment variablesSystem/shellexport MARKETDATA_TOKEN=...
3Client defaultsPer-client instanceclient.default_params.output_format = "json"
4 (highest)Method parametersPer-requestclient.stocks.prices("AAPL", output_format="csv")

SDKs should automatically load .env files from the current working directory if present.

How It Works​

When making a request, merge configuration in order:

  1. Load .env file values (if present)
  2. Override with system environment variables
  3. Override with client default parameters
  4. Override with direct method parameters

Example Flow​

.env file: MARKETDATA_OUTPUT_FORMAT=internal
Shell env: MARKETDATA_OUTPUT_FORMAT=json
Client default: output_format=dataframe
Method call: client.stocks.prices("AAPL", output_format="csv")

Result: output_format="csv" (method parameter wins)

Supported Environment Variables​

VariablePurposeDefault
MARKETDATA_TOKENAPI authentication token(none)
MARKETDATA_BASE_URLAPI base URLhttps://api.marketdata.app
MARKETDATA_API_VERSIONAPI versionv1
MARKETDATA_LOGGING_LEVELSDK logging levelINFO
MARKETDATA_OUTPUT_FORMATDefault output format(language default)
MARKETDATA_DATE_FORMATDefault date formattimestamp
MARKETDATA_COLUMNSColumns to include(all)
MARKETDATA_ADD_HEADERSInclude headers in CSVtrue
MARKETDATA_USE_HUMAN_READABLEHuman-readable field namesfalse
MARKETDATA_MODEData mode (live/cached/delayed)live

5. Authentication​

Requirements​

  • Set Authorization: Bearer {token} header on all requests
  • Token resolution follows the configuration cascade:
    1. .env file (lowest)
    2. System environment variable MARKETDATA_TOKEN
    3. Client constructor parameter (highest)
  • Validate token on startup by default: SDKs should call /user/ during client initialization and fail fast on invalid tokens.
  • Constructor override required: SDKs must provide a client-constructor option to disable startup validation for short-lived runtimes (e.g., serverless/CLI jobs). When disabled, token/auth errors must surface on the first authenticated request.
  • Never log tokens - redact in all log output

Demo Mode​

When no token is provided:

  • Omit Authorization header
  • Log a warning about demo mode limitations
  • Skip rate limit initialization

6. Error Handling​

6.1 Error Taxonomy​

All SDKs must define these error types:

Error TypeWhen to Use
AuthenticationError401 responses, invalid/missing token
BadRequestError400 responses, invalid parameters
PaymentRequiredError402 responses, request valid but not included in the plan
ForbiddenError403 responses, token rejected for this request
NotFoundError404 responses
RateLimitError429 responses, rate limit exceeded
ServerError5xx responses
NetworkErrorConnection failures, timeouts
ParseErrorFailed to parse response

Use the naming convention the language already uses for its other error types. Go returns a sentinel alongside the type, so PaymentRequiredError pairs with ErrPaymentRequired and ForbiddenError with ErrForbidden.

Why 402 is its own type. A 402 says the token is valid and the request is well-formed — only the plan does not cover it. That is a different fact from a 401, where no usable identity was presented, and from a 403, where an identity was presented and refused. A caller that must tell "you may not ask this" apart from "you asked this wrong" cannot do so once all three collapse into one class, and must not have to read .status_code off a generic HTTP error to find out. Capability and entitlement discovery is the concrete case: a consumer probing what its plan allows needs 402 to be a typed, catchable answer. See 402: Payment Required for the canonical semantics.

6.2 Support Context​

Every error must include these fields for support staff troubleshooting:

FieldSourceExample
request_idcf-ray response header"8a1b2c3d4e5f6g7h-SJC"
request_urlFull request URL"https://api.marketdata.app/v1/stocks/quotes/"
status_codeHTTP status code429
timestampSDK-generated (US/Eastern)"2025-02-21 12:00:00"
messageError description"Rate limit exceeded"
exception_typeError class name"RateLimitError"

In exception-based SDKs (Python, PHP, Java, C#, JavaScript/TypeScript), these support-context fields must be properties on the thrown exception object. In returned-error SDKs (Go, Rust), these fields must be present on the returned error value/type.

6.3 Support Info Helper​

SDKs must provide a formatted support_info string for users to send to support:

--- MARKET DATA SUPPORT INFO ---
request_id: 8a1b2c3d4e5f6g7h-SJC
request_url: https://api.marketdata.app/v1/stocks/quotes/
status_code: 429
timestamp: 2025-02-21 12:00:00
message: Rate limit exceeded
exception_type: RateLimitError
--------------------------------

This must be accessible via error.support_info or equivalent.

6.4 Language-Specific Patterns​

Follow the idiomatic error handling pattern for each language:

LanguagePatternExample
PythonRaise exceptionsraise RateLimitError(...)
JavaScriptReject promisesthrow new RateLimitError(...)
PHPThrow exceptionsthrow new RateLimitException(...)
GoReturn errorreturn nil, &RateLimitError{...}
JavaThrow exceptionsthrow new RateLimitException(...)
C#Throw exceptionsthrow new RateLimitException(...)
RustReturn ResultErr(MarketDataError::RateLimit {...})

Do not force exception-based error handling onto Go or Rust. Do not force Result types onto Python or PHP.


7. Logging​

Log Format​

Use a consistent log format across all SDKs:

{timestamp} - {logger_name} - {level} - {message}

Example: 2025-02-21 12:00:00 - marketdata.client - INFO - Making request to /v1/stocks/quotes/

Log Levels​

Configurable via MARKETDATA_LOGGING_LEVEL environment variable:

LevelWhat to Log
DEBUGToken (redacted), request details, response headers
INFOClient initialization, base URL, API version
WARNINGDemo mode, deprecated features
ERRORRequest failures, rate limit errors

Required Log Points​

  • Client initialization (INFO)
  • Token value - redacted (DEBUG): show only last 4 chars
  • Request URL (DEBUG)
  • Response status and timing (DEBUG/INFO based on status)
  • Errors with support context (ERROR)

Token Redaction​

Never log full tokens. Redact to show only last 4 characters:

Token: ************************************YKT0

8. Rate Limiting​

8.1 Initialization​

On client creation (with valid token):

  1. Call GET /user/ to fetch rate limit info
  2. Store initial credit state: credit_limit, credits_remaining, reset_time, credits_consumed

8.2 Tracking​

After every request:

  1. Parse rate limit headers from response:
    • x-api-ratelimit-limit
    • x-api-ratelimit-remaining
    • x-api-ratelimit-reset
    • x-api-ratelimit-consumed
  2. Attach parsed rate limit metadata to the returned response/result object (request-scoped)
  3. Update client-level rate limit state as a convenience latest snapshot

8.3 Pre-flight Check​

Before each request:

  1. Check if credits_remaining <= 0
  2. If exhausted, throw RateLimitError immediately (don't make the request)

8.4 Expose State​

Provide method to check current rate limits:

client.rate_limits
// Returns: { limit: 50000, remaining: 49500, reset: 1708531200, consumed: 1 }

client.rate_limits is a client-level latest snapshot and is not request-scoped. In concurrent/async execution, this value is non-deterministic (last completed request wins). Per-request logic must use the rate limit metadata attached to each returned response/result.


9. Retries and Backoff​

9.1 HTTP Status Code Handling​

Status CodeAction
200Success - parse response
203Success - non-authoritative (cached data) - parse response
400Throw BadRequestError - do not retry
401Throw AuthenticationError - fail immediately, do not retry
402Throw PaymentRequiredError - do not retry
403Throw ForbiddenError - do not retry
404Return empty/no-data response (not an error for most endpoints)
429Throw RateLimitError - do not retry, expose retry-after
500Throw ServerError - do not retry
501-599Retry with exponential backoff

Note on 404: Most endpoints return 404 when no data exists (e.g., no quotes for a delisted symbol). SDKs should return an empty result object with no_data = true rather than throwing an exception.

Note on 402: Unlike 404, a 402 must never be returned as an empty or no-data result. 402: Payment Required states that the request itself is valid — the API declined to answer it, and 402 is that refusal. Rendering it as no-data tells the caller the data does not exist, which is false, and it fails silently: there is no exception anywhere for the caller to catch. An SDK that probes a customer's history depth by walking back until the API stops answering reads a plan denial as the end of the archive, and silently truncates that customer's price history. 402 must throw.

Note on 402 and 403 retries: Both rows forbid the SDK's automatic retry, and they are terminal for different lengths of time. A 402 stands until the plan changes, so an immediate retry only spends a request. A 403 is terminal for that request, but the condition behind it can clear on its own — the common cause is the multi-IP block, which lifts after about 5 minutes. The SDK must not retry either code itself; the caller may try a 403 again once the block clears.

9.2 Retry Conditions​

Retry requests only when:

  • HTTP status > 500 (server errors)
  • Network/connection errors

Never retry: 4xx errors, rate limit errors

9.3 Backoff Strategy​

  • Use exponential backoff: initial * 2^retry, where retry is the 0-indexed retry number (0 = first retry, 1 = second retry, 2 = third retry)
  • Initial delay: 1 second (fixed)
  • Default max retries: 3 (in addition to the initial attempt, for up to 4 total attempts)
  • Backoff schedule at the default of 3 retries:
    • Before retry 0 (1st retry): 1s (1 * 2^0)
    • Before retry 1 (2nd retry): 2s (1 * 2^1)
    • Before retry 2 (3rd retry): 4s (1 * 2^2)

Max retries is user-configurable via a client constructor parameter (e.g., max_retries). The default applies when not specified. The initial delay and exponential base are fixed and not configurable.

9.4 Retry-After Header​

If response includes Retry-After header:

  • Respect the server-specified delay
  • Override calculated backoff with server value

9.5 API Status Check​

Before each retry attempt for retryable server errors (501-599), apply this status-check workflow:

  1. Use cached /status/ data with two thresholds:
    • Refresh interval: 270 seconds (4.5 minutes)
    • Cache validity: 300 seconds (5 minutes)
  2. If cache age is < 270s: use cached status, no refresh.
  3. If cache age is 270s to < 300s: use cached status and trigger non-blocking async refresh.
  4. If cache is stale or empty (>= 300s): trigger non-blocking async refresh and treat current status as unknown for this attempt.
  5. Retry decision:
    • If service status is offline, do not retry - fail immediately.
    • If service status is online or unknown, proceed with retry/backoff.

Do not perform blocking status refresh inside retry loops.


10. Timeouts​

Requirements​

  • Default request timeout: 99 seconds
  • Timeout is fixed and must not be configurable
  • If the HTTP client supports a separate connection timeout, set connection timeout to 2 seconds (fixed)
  • If separate connection timeout is not supported, use the fixed 99-second request timeout
  • Apply timeout to all HTTP requests

11. Output Formats​

11.1 Wire Format Decoding (Required)​

The API wire format uses compressed, array-keyed JSON. SDKs must decode this into user-usable typed models before returning results.

  • Expand compressed response payloads into named fields
  • Return idiomatic typed models for the target language
  • Do not expose raw compressed arrays as the default SDK output
  • Raw compressed payloads may be exposed only as an explicit opt-in debug/raw mode

11.2 Language-Specific Defaults​

Each SDK should return data in the format most natural for that language ecosystem:

LanguageDefault OutputNotes
Pythonpandas DataFrameDataFrames are the standard for financial data in Python
Rdata.frame / tibbleDataFrames are standard in R workflows
JavaScriptPlain objects/arraysNative JS objects, easily JSON-serializable
PHPObjects or associative arraysFollow PHP conventions
GoTyped structsStrongly typed, no reflection magic
JavaPOJOsStandard Java bean patterns
C#Typed objects.NET conventions
RustTyped structsWith serde for serialization

11.3 Additional Format Support​

SDKs may support additional formats only when they are idiomatic for that language:

Language / EcosystemAdditional Formats to Support
PythonOptional Polars DataFrame output
ROptional tibble/data.table output
JavaScript/TypeScriptPlain JSON objects/arrays (no DataFrame requirement)
PHPObjects/arrays and optional CSV export
Go/Java/C#/RustTyped models; no DataFrame requirement

Do not add ecosystem-misaligned formats just for parity across SDKs.

11.4 Date Handling​

  • Parse Unix timestamps to the language's native datetime type
  • Normalize to US/Eastern timezone by default
  • Respect dateformat parameter for output

11.5 Monetary Value Precision​

Money must not be represented as a binary floating-point number in typed models when the language provides an exact decimal type.

Binary floats cannot represent decimal money exactly (0.1 + 0.2 != 0.3), so any comparison, rounding, or accumulation a customer performs on SDK output drifts. Prices are the primary product of this API; they must round-trip exactly.

Language Applicability​

LanguageExact Decimal TypeMonetary Fields
Pythondecimal.DecimalMust use decimal
Javajava.math.BigDecimalMust use decimal
C#decimalMust use decimal
GoNone nativeExempt — float64
RustNone nativeExempt — f64
PHPNone nativeExempt — float
JavaScript/TypeScriptNone nativeExempt — number
RNone nativeExempt — numeric

Exempt SDKs must not add a third-party decimal library to satisfy this requirement. An ecosystem-foreign money type in public model fields conflicts with the Language-Idiomatic Design principle and forces a dependency on every consumer. Float is the correct choice where the language offers no exact decimal type.

Preserve Precision at the Parse Boundary​

Converting an already-parsed float to a decimal does not satisfy this requirement. The exact value is destroyed at the JSON decode step, and a later conversion only locks in the float error. SDKs subject to this requirement must decode the response so the API's decimal string reaches the decimal type directly.

LanguageDecimal-Preserving Decode
Pythonjson.loads(response.text, parse_float=Decimal)
JavaJackson DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS
C#System.Text.Json reader → GetDecimal() on the raw token

What Counts as Money​

The rule: if the value is denominated in currency, it is money. Ratios, statistics, and counts are not.

  • Monetary (decimal): bid, ask, mid, last, change, open/high/low/close, strike, intrinsic value, extrinsic value, underlying price, and EPS figures (reported, estimated, surprise)
  • Not monetary (float or int): Greeks (delta, gamma, theta, vega), implied volatility, percentages (e.g. changepct, surpriseEPSpct), and sizes/counts (bidSize, askSize, volume, openInterest, dte)

Canonical REST documentation remains the source of truth for which fields exist on each endpoint; apply the rule above to that schema.

Format Exemptions​

This requirement governs typed models only. The following output formats are exempt:

Output FormatMoney TypeWhy Exempt
DataFrame (pandas, polars, R)float64Analytics surface for vectorized math; pandas has no decimal dtype
CSVRaw textAlready byte-faithful exact decimal strings straight from the API
JSONNative decode"JSON output" means the decoded object; exactness is the typed model's job

On DataFrames: a DataFrame is an analytics surface — users load data to run vectorized statistics (returns, rolling means, volatility). In pandas, decimals degrade to object dtype, which kills vectorized math on the default output. Where a backend does offer a decimal dtype (polars), still use float64: making only one backend exact would mean identical code returns different dtypes depending on which optional dependency is installed. Consistency across interchangeable backends wins.

Customers who need exactness go through the typed model. SDK documentation must state plainly which formats are exact and which are not.

Arithmetic​

SDKs must not perform their own price arithmetic. Computed values (mid, change, and similar) are supplied by the API and are passed through as received. This requirement is about representation, not calculation.

11.6 Response Object Features​

All response objects must provide:

Format Detection Methods​

MethodReturns
isJson()true if response is JSON format
isCsv()true if response is CSV format
isHtml()true if response is HTML format

File Saving​

response.saveToFile("/path/to/output.csv")
  • Save response data to a file
  • Automatically determine format from extension or response type
  • Return the file path or a success indicator

No-Data Detection​

response.no_data // or response.hasData()
  • Indicate when a 404 response returned no data
  • Allow callers to distinguish "no data" from errors

String Conversion (Required)​

All public response models must support idiomatic string conversion for fast inspection/logging in example code and day-to-day usage.

  • Python: implement __str__ (and __repr__ where appropriate)
  • PHP: implement __toString()
  • JavaScript/TypeScript: implement toString()
  • Go: implement String() on formatter types where applicable
  • Java/C#: provide toString() / ToString()
  • Rust: implement Display (and derive/provide Debug as needed)

String conversion should produce concise, developer-friendly summaries (not raw compressed payloads).


12. Concurrency Helpers​

Global Concurrency Pool​

SDKs must enforce a maximum of 50 concurrent requests at the client level using a sliding window (not batching):

  • All endpoints share a single concurrency pool
  • Maintain exactly 50 active requests at all times (when demand exists)
  • As soon as one request completes, start the next immediately
  • Not batching: Don't wait for all 50 to finish before starting more
  • Must be thread-safe / goroutine-safe / async-safe depending on language
# Correct (sliding window):
# Request 1 completes → Request 51 starts immediately
# Request 2 completes → Request 52 starts immediately
# Always 50 in-flight until queue is exhausted

# Wrong (batching):
# Wait for all 50 to complete → Start next 50
# This wastes time waiting for slowest request

stocks.candles() - Date Range Splitting​

For intraday resolutions with date ranges > 1 year:

  1. Split into year-sized chunks
  2. Fetch chunks concurrently (respects global pool limit)
  3. Merge results into single response

options.quotes() - Batch Processing​

For multiple option symbols:

  1. Fetch quotes concurrently (respects global pool limit)
  2. Merge results into single response

13. Testing Requirements​

Unit Tests (No Real API Calls)​

Unit tests must use mocks for all HTTP requests - never hit the real API:

  • Use language-appropriate HTTP mocking library
  • Mock /user/ and /status/ endpoints in test fixtures
  • Test both success and error scenarios for each endpoint

Must cover:

  • URL construction for all endpoints
  • Parameter validation and error messages
  • Error type mapping (status code → error type)
  • Retry behavior and backoff timing
  • Rate limit header parsing
  • Request-scoped rate limit metadata (consumed included) under concurrent requests
  • Response parsing and typed model creation
  • Date/time normalization

Integration Tests (Real API Calls)​

Integration tests make actual REST requests to the live API:

  • Gated by environment variable (e.g., MARKETDATA_RUN_INTEGRATION_TESTS=true)
  • Require valid API token
  • Test real request/response cycles
  • Verify actual API behavior matches expectations
  • Run in CI by default for all pull requests and release pipelines
  • Not run for every commit/push and not part of standard default/local test runs

Minimum coverage: at least one live test per endpoint​

Every endpoint in §2.2 must have at least one integration test that calls it against the live API. A mocked test does not satisfy this: a mock proves the SDK parses the response the author expected, which is precisely the assumption that goes stale when the API changes. Only a live call can tell you the contract still holds.

ResourceEndpoints requiring live coverage
stocksprices, quotes, candles, earnings, news
optionschain, expirations, quotes, lookup
fundscandles
marketsstatus
utilitiesstatus, headers, user

Each test must assert on the shape of the decoded response — that the fields the SDK models actually arrived and carry usable values — not merely that the call returned a 2xx. A test that checks only the status code passes against an endpoint that has silently started returning an empty body.

Where an endpoint has materially different response shapes, cover each one: a no-data answer is a different contract from a populated one, and both are worth a witness.

A missing token must fail the suite, never skip it. An integration suite that skips when unauthenticated reports success while testing nothing, which is worse than having no suite at all — it produces a green check that means nothing. If the token is absent, fail and say so.

Code Coverage​

  • 100% code coverage required
  • Lines that cannot be tested must be explicitly marked with coverage ignore comments
    • Python: # pragma: no cover
    • PHP: // @codeCoverageIgnore
    • Go: Use build tags or explicit skip
    • Use language-appropriate ignore syntax
  • Coverage ignore must include a comment explaining why

CI Requirements​

  • Run unit tests on every PR and push to main
  • Run on multiple runtime versions
  • Produce coverage reports (must be 100% or CI fails)
  • Integration tests: required on every PR and in release pipelines; excluded from per-commit/push jobs and default test runs

14. Documentation Requirements​

README Must Include​

  • Installation instructions
  • Quick start example showing a complete request in 2–3 lines of code
  • Environment variable configuration
  • Error handling example
  • String conversion example(s) showing ergonomic object output
  • Link to full documentation

API Documentation​

  • Document all public methods
  • Include parameter types and descriptions
  • Show example usage for each endpoint
  • Include examples that demonstrate string conversion on response models
  • Document error types and when they occur

15. Packaging Requirements​

Version & Changelog​

  • Use Semantic Versioning (MAJOR.MINOR.PATCH)
  • Maintain CHANGELOG.md following Keep a Changelog format
  • Version must be automatically detected from the package manager metadata (e.g., composer.json, setup.py, go.mod) - do not hardcode version strings

Package Registry​

LanguageRegistry
PythonPyPI
JavaScriptnpm
JavaMaven Central
GoGo modules (pkg.go.dev)
C#NuGet
Rustcrates.io

License​

  • MIT license required
  • Include LICENSE file in distribution

16. Security Requirements​

  • Never log API tokens - redact/obfuscate in all output
  • Support environment variable token loading (no hardcoding)
  • Error messages must not contain sensitive data
  • Validate TLS certificates (no skip-verify options)

17. Repository and Release Configuration​

Sections 1–16 govern the SDK's code. This section governs the repository that ships it. These items are invisible in a code review and are exactly the ones that get skipped: a documented label that was never created, a security policy pointing at a disabled button, a release cut by hand because no workflow existed.

Every SDK repository must satisfy this section, regardless of language.

17.1 Required Repository Files​

PathPurpose
SECURITY.mdVulnerability reporting instructions and the security fix policy
.github/RELEASE_PROCESS.mdHow to cut a release for this SDK
.github/ISSUE_WORKFLOW.mdHow a maintainer triages an incoming bug report
.github/BUG_FINDING.mdHow to hunt for bugs proactively, before users hit them
.github/ISSUE_TEMPLATE/bug.ymlStructured bug report form
.github/ISSUE_TEMPLATE/config.ymlContact links; blank_issues_enabled: false
.github/dependabot.ymlDependency updates (see §17.4)
.github/FUNDING.ymlgithub: MarketDataApp
CHANGELOG.mdKeep a Changelog format (see §15)
LICENSEMIT (see §15)

These are language-adapted, not copied. A bug template must ask for the runtime version, package manager and platform values that actually exist for that language, and must name real methods and error types from that SDK. A template that asks a Go user for their target framework is worse than no template.

17.2 Repository Settings​

  • Private Vulnerability Reporting is enabled. SECURITY.md tells reporters to use Security → "Report a vulnerability". If the setting is off, that button does not exist and the instruction is a dead end.
  • Every label referenced by ISSUE_WORKFLOW.md exists. At minimum bug, accepted, needs-info, wontfix, dependencies. A documented triage step that applies a non-existent label fails on execution.
  • Description is set and names the language and what the SDK covers.
  • Topics are set — at minimum the language, sdk, and the asset classes the SDK actually supports. Remove topics for resources the SDK does not cover; they misrepresent the package in search.
  • ISSUE_TEMPLATE/config.yml links only to destinations that exist. If Discussions are disabled on the repository, the config must not link to them.
  • Default branch is main, with a development branch when the CI workflows reference one.
  • Branch protection on the default branch, or a deliberate, recorded decision not to have it. RELEASE_PROCESS.md must state which, because it determines whether a release merge needs an approval.

17.3 Required Secrets​

SecretRequiredPurpose
MARKETDATA_TOKENYesLive integration suite (§13). Its absence must fail the job, never skip it
CODECOV_TOKENOptionalCoverage badge; absence must not break CI
Registry credentialsPer languagePublishing (see §15) — e.g. Maven Central signing keys, NuGet Trusted Publishing

A skipped integration suite must never report success. The failure mode this prevents is a green pipeline that ran nothing. If the token is absent, the job fails and says so.

17.4 Dependency Updates​

  • dependabot.yml covers the CI ecosystem (github-actions) and every package manifest in the repository — including example or sample applications that carry their own manifest.
  • Updates are grouped. Ungrouped Dependabot opens one pull request per dependency per manifest. Where several sample apps share a dependency set, one upstream release becomes a wall of near-identical pull requests and the updates stop being read.
  • Major versions are kept out of the routine group — either in a group of their own or as individual pull requests — so routine bumps merge without waiting on a major, and each major still gets read on its own.
  • Dependencies satisfied by a local path override are ignored (for example a Go replace directive, or an npm file: dependency). A pull request bumping one changes nothing and can never merge.
  • Dependabot pull requests carry the dependencies label.

17.5 Release Automation​

Every SDK needs a release workflow. Cutting a release by hand is not acceptable as a steady state: the steps that get skipped under pressure are exactly the ones that matter.

  • A tag-and-release workflow exists, triggered only by manual workflow_dispatch. No push, merge, or tag event may reach it.
  • It requires a typed confirmation input (confirm: RELEASE).
  • It validates before spending runner time: the version is well-formed SemVer, the tag does not already exist, and CHANGELOG.md contains the section for the version.
  • It runs the full test suite, including live integration tests, before the tag is cut, against the exact ref being released. Reuse the existing CI workflow rather than copying it — a second copy of the suite will drift.
  • Release notes are extracted from CHANGELOG.md, never written by hand at release time and never generated back into the changelog. The heading format is matched exactly (## [X.Y.Z]), so the changelog must carry a ## [Unreleased] section for maintainers to promote.
  • The workflow resolves the ref to a concrete commit SHA and tags that SHA, so a branch moving mid-run cannot change what ships.
  • It verifies the release after publishing: the artifact resolves from the public registry, and a throwaway project can install it and report the expected version.

A tag or release created by a workflow using the default GITHUB_TOKEN does not start another workflow run. GitHub suppresses this to prevent recursive triggering. Any workflow gated on release: published or push: tags therefore stays silent for an automated release. Do not rely on it to test or publish; chain the work explicitly from the release workflow, and say so in RELEASE_PROCESS.md.

Registries and irreversibility​

RELEASE_PROCESS.md must state where the point of no return is and what the remedy is, because it differs sharply by language:

LanguagePublicationCan a bad version be withdrawn?
Python, JavaScript, Java, C#, PHPUpload to a registryNo — versions are immutable. Unlist or yank where supported, then ship a patch
GoThe git tag itself. proxy.golang.org reads the tag; sum.golang.org logs the hashNo. Deleting the tag does not withdraw it. The only remedy is a later version carrying a retract directive

Go has no publish step and no staging feed: the moment the tag exists and one person fetches it, the version is public and permanent. Its release workflow therefore ends in verification rather than upload, and all validation must precede the tag.

17.6 Verifying a Repository​

These checks are mechanical and should be run against every SDK repository periodically, not only at acceptance.

REPO=MarketDataApp/sdk-<lang>

# Required files
for f in SECURITY.md .github/RELEASE_PROCESS.md .github/ISSUE_WORKFLOW.md \
.github/BUG_FINDING.md .github/dependabot.yml .github/FUNDING.yml \
.github/ISSUE_TEMPLATE/bug.yml .github/ISSUE_TEMPLATE/config.yml; do
gh api "repos/$REPO/contents/$f" --jq '.name' >/dev/null 2>&1 \
&& echo "ok $f" || echo "MISSING $f"
done

# Private Vulnerability Reporting
gh api "repos/$REPO/private-vulnerability-reporting" --jq '.enabled'

# Labels the triage workflow depends on
for l in bug accepted needs-info wontfix dependencies; do
gh api "repos/$REPO/labels/$l" --jq '.name' >/dev/null 2>&1 \
&& echo "ok label:$l" || echo "MISSING label:$l"
done

# Integration token, description, topics
gh api "repos/$REPO/actions/secrets" --jq '[.secrets[].name] | join(", ")'
gh api "repos/$REPO" --jq '{description, topics: (.topics | length), discussions: .has_discussions}'

Acceptance Checklist​

Before accepting an SDK, verify:

Core Functionality​

  • Every endpoint works in 2–3 lines of code (env auth + method call + print)
  • All required SDK method capabilities implemented (as defined in this doc and canonical API docs)
  • Bearer authentication works
  • Token validation defaults to startup (fail fast) and supports constructor override to disable startup validation
  • Environment variable configuration works
  • Configuration cascade (env → client defaults → method params)
  • Rate limits are tracked and exposed
  • Errors are typed with support context
  • 401 errors fail immediately (no retry)
  • 404 returns no-data response (not exception)
  • Retries use exponential backoff (>500 only)
  • Fixed timeout policy enforced (99s request timeout; 2s connect timeout when supported)
  • Concurrency pool enforced (50 max sliding window)
  • User-Agent header set correctly

Code Quality​

  • Unit tests exist and pass (mocked, no real API calls)
  • Integration tests exist (gated by env var)
  • At least one live integration test per §2.2 endpoint, asserting on the decoded response shape
  • A missing API token fails the integration suite rather than skipping it
  • 100% code coverage (or explicit ignores with comments)
  • CI pipeline configured
  • Code follows language style guide (PEP 8, PSR-12, etc.)
  • Passes standard linter for language
  • No security issues (token leakage, etc.)

Response Objects​

  • Format detection methods (isJson, isCsv, isHtml)
  • File saving capability (saveToFile)
  • No-data detection property
  • Monetary fields use the language's exact decimal type, or the language is exempt per §11.5
  • JSON decoding preserves decimal precision (no float round-trip before the model)
  • Non-monetary fields (Greeks, IV, percentages, sizes) remain float/int
  • DataFrame output remains float64 on every supported backend

Documentation​

  • README with quick start
  • All methods documented
  • Examples provided

Distribution​

  • Published to appropriate registry
  • SemVer versioning
  • Version auto-detected from package metadata
  • Changelog exists
  • MIT license included

Repository & Release Configuration​

  • All required repository files present (§17.1), language-adapted rather than copied
  • Private Vulnerability Reporting enabled
  • Every label referenced by ISSUE_WORKFLOW.md exists in the repository
  • Description and topics set, and topics do not claim unsupported resources
  • ISSUE_TEMPLATE/config.yml links only to destinations that exist
  • MARKETDATA_TOKEN secret present; a missing token fails the integration job rather than skipping it
  • Dependabot covers every manifest, groups its updates, and separates majors
  • Manual-dispatch release workflow with typed confirmation, gated on the full suite
  • Release notes extracted from CHANGELOG.md; an ## [Unreleased] section exists
  • RELEASE_PROCESS.md states where the point of no return is and how to remedy a bad release