# Market Data Account
Source: https://www.marketdata.app/docs/account/index.md
Manage your Market Data Account through the [Customer Dashboard](https://www.marketdata.app/dashboard/).
---
# Billing Portal
Source: https://www.marketdata.app/docs/account/billing-portal/index.md
For heightened security, Market Data uses an independent third-party billing system to process your transactions. We do not store your credit card information and we do not have access to your billing details. Customers can manage their own subscriptions and billing details via the [Billing Portal](https://cc.payproglobal.com/Customer/Account/Login).
## How to Access the Billing Portal for the First Time
> [!NOTE]
> The Billing Portal is only available to **paid customers**. A billing portal account is created automatically when you make your first purchase. If you have a free or trial account and want to subscribe, you can do so directly from the Market Data [Customer Dashboard](https://www.marketdata.app/dashboard/).
To access your billing information for the first time, follow these steps:
1. Visit the [Billing Portal](https://cc.payproglobal.com/Customer/Account/Login).
2. Since it's your first time, you'll need to set up a password. Click on the "Forgot Password" link to initiate this process. Please note that the [Billing Portal](https://cc.payproglobal.com/Customer/Account/Login) operates independently from your Market Data account, meaning your Market Data credentials will not work here.
3. After setting your password, log in with your new credentials.
4. You will now be able to view and manage your billing details and subscriptions.
## Actions You Can Take in the Billing Portal
The [Billing Portal](https://cc.payproglobal.com/Customer/Account/Login) allows you to take any of the following actions:
- View your order details
- Generate a PDF invoice for your subscription charges
- Update your credit card information
- Add a back-up credit card
- Request billing support
- Cancel your subscription
## Billing Support
You can also request assistance with billing-related issues after logging-in to the [Billing Portal](https://cc.payproglobal.com/Customer/Account/Login). Customers request assistance via:
- Support ticket
- Online chat
- Schedule a call-back
### Limitations of Billing Support
Please be aware that billing support staff can only assist you with billing-related questions. For technical issues, API troubleshooting, assistance with Sheets formulas, etc., please open a support ticket in the Market Data [Customer Dashboard](https://www.marketdata.app/dashboard/).
Billing staff cannot assist with custom quotes, student discounts, or other special billing requests. For these types of inquiries, please contact our sales team via the Market Data [Customer Dashboard](https://www.marketdata.app/dashboard/).
---
# Cancellations
Source: https://www.marketdata.app/docs/account/cancellations/index.md
Your Market Data account can be cancelled at any time, directly from your member page.
1. Login and go to your [member page](https://dashboard.marketdata.app/marketdata/member).
2. Find your subscription. A **cancel** button appears on the subscription itself, next to **upgrade**.
3. Click **cancel** and confirm.
4. You will receive a confirmation email once your subscription has been cancelled.
> [!TIP]
> If your decision to cancel is due to technical issues, missing features, or any other reason, we invite you to first open a support ticket in the [Customer Dashboard](https://www.marketdata.app/dashboard/) before cancelling. Our team is here to help and we will do everything we can to resolve your issue and make sure you're happy with Market Data.
## Cancelling from the Billing Portal
You can also cancel from the Billing Portal, though this requires a separate login. Use it if you cannot reach your member page.
1. Login to the [Billing Portal](https://cc.payproglobal.com/Customer/Account/Login).
2. If you have never logged-in to the billing portal before, [see our support page](https://www.marketdata.app/docs/account/billing-portal) on how to set-up access for the first time.
3. Navigate to the "Subscriptions" tab on the left sidebar.
4. Select your Market Data subscription and cancel it.
5. You will receive a confirmation email once your subscription has been cancelled.
## Unused Time
When you cancel a paid subscription, you will continue to have access to Market Data until your account's renewal date. Feel free to continue utilizing Market Data's services until that time. After the renewal date passes, you will not lose access. Your account will immediately revert to a `Free Forever` subscription, allowing you to continue making 100 API calls or 100 Sheets formulas per day.
## Restarting a Cancelled Subscription
If your subscription has not yet expired, you may be able to restart it. Open a Billing ticket in the [Customer Dashboard](https://www.marketdata.app/dashboard/) and we'll reverse your cancellation. This will allow billing to continue and ensure uninterrupted access to Market Data. If Market Data pricing has changed, restarting an unexpired subscription will allow you to maintain your previous plan's pricing.
If your subscription has already expired, it cannot be restarted. Please make a new purchase and a new subscription will be created for your account. It is important to note that once a subscription has expired, any new subscription will always be made using the current pricing.
---
# Data Freshness
Source: https://www.marketdata.app/docs/account/data-freshness/index.md
Market Data classifies the data you receive into three freshness categories:
- **Real-time** — under 15 minutes old. Live trading data.
- **Delayed** — 15+ minutes old, but from the current trading session.
- **Historical** — from a previous, fully-closed trading session.
Which category applies depends on your plan, the data type, and the time of day.
## When Delayed data becomes Historical
Historical requires both a >15-minute delay floor *and* a data-type-specific session-closed condition. The session-closed condition is **different for stocks and options**:
- **Stocks:** Historical at session close + 15 min — **4:15:01 PM ET** on a regular trading day. Between 4:00 PM and 4:15 PM ET, the data is still Delayed (the 15-minute Delayed window hasn't elapsed yet).
- **Options:** Historical at the *next* session's open — **9:30:01 AM ET** the next trading day, not at the prior session's close.
Friday's options quotes therefore do **not** become Historical until **9:30:01 AM ET Monday** — they remain Delayed all weekend.
If you query an options endpoint at 6:33 AM ET Wednesday on a plan that provides Historical-only options data, you will receive **Monday's** close, not Tuesday's. Tuesday's options data does not roll to Historical until 9:30:01 AM ET Wednesday. This is the most common cause of "the data doesn't match my broker" support requests — the behavior is correct, the customer is just querying before the next session has opened.
## By Plan
The tables below show the freshness category for every API endpoint, by plan, and assume non-professional status. Real-time exchange data is never available to professional subscribers — see the [Notes](#notes) below. For the underlying entitlement model, see [Exchange Entitlements](https://www.marketdata.app/docs/account/entitlements).
> [!NOTE]
> **[Real-time stock pricing comes from `/v1/stocks/prices/`]**
>
> [`/v1/stocks/prices/`](https://www.marketdata.app/docs/api/stocks/prices) delivers Real-time stock prices with no delay on every paid plan. `/v1/stocks/quotes/`, `/v1/stocks/candles/`, and `/v1/stocks/bulkcandles/` carry the standard 15-minute exchange delay. Point your application at `/v1/stocks/prices/` when you need the current price.
### Free Forever
All pricing data is Historical (24-hour delayed). Metadata endpoints remain Real-time.
| Endpoint | Freshness | Notes |
|----------------------------|------------|------------------------------|
| `/v1/stocks/quotes/` | Historical | |
| `/v1/stocks/candles/` | Historical | |
| `/v1/stocks/bulkcandles/` | Historical | |
| `/v1/stocks/prices/` | Historical | |
| `/v1/options/quotes/` | Historical | |
| `/v1/options/chain/` | Historical | |
| `/v1/options/expirations/` | Real-time | Contract metadata |
| `/v1/options/lookup/` | Real-time | Contract metadata |
| `/v1/markets/status/` | Real-time | Calendar metadata |
| `/v1/funds/*` | See note | Pending product confirmation |
### Starter Trial
Same as Free Forever — all pricing data is Historical (24-hour delayed).
| Endpoint | Freshness | Notes |
|----------------------------|------------|------------------------------|
| `/v1/stocks/quotes/` | Historical | |
| `/v1/stocks/candles/` | Historical | |
| `/v1/stocks/bulkcandles/` | Historical | |
| `/v1/stocks/prices/` | Historical | |
| `/v1/options/quotes/` | Historical | |
| `/v1/options/chain/` | Historical | |
| `/v1/options/expirations/` | Real-time | Contract metadata |
| `/v1/options/lookup/` | Real-time | Contract metadata |
| `/v1/markets/status/` | Real-time | Calendar metadata |
| `/v1/funds/*` | See note | Pending product confirmation |
### Trader Trial
Same as Starter Trial — all pricing data is Historical (24-hour delayed).
| Endpoint | Freshness | Notes |
|----------------------------|------------|------------------------------|
| `/v1/stocks/quotes/` | Historical | |
| `/v1/stocks/candles/` | Historical | |
| `/v1/stocks/bulkcandles/` | Historical | |
| `/v1/stocks/prices/` | Historical | |
| `/v1/options/quotes/` | Historical | |
| `/v1/options/chain/` | Historical | |
| `/v1/options/expirations/` | Real-time | Contract metadata |
| `/v1/options/lookup/` | Real-time | Contract metadata |
| `/v1/markets/status/` | Real-time | Calendar metadata |
| `/v1/funds/*` | See note | Pending product confirmation |
### Starter
Real-time stock prices. Stock quotes, stock candles, and options data carry the 15-minute exchange delay.
| Endpoint | Freshness | Notes |
|----------------------------|-------------|------------------------------|
| `/v1/stocks/quotes/` | 15m delayed | |
| `/v1/stocks/candles/` | 15m delayed | |
| `/v1/stocks/bulkcandles/` | 15m delayed | |
| `/v1/stocks/prices/` | Real-time | |
| `/v1/options/quotes/` | 15m delayed | |
| `/v1/options/chain/` | 15m delayed | |
| `/v1/options/expirations/` | Real-time | Contract metadata |
| `/v1/options/lookup/` | Real-time | Contract metadata |
| `/v1/markets/status/` | Real-time | Calendar metadata |
| `/v1/funds/*` | See note | Pending product confirmation |
### Trader
Real-time stock prices and Real-time options data. Stock quotes and candles carry the 15-minute exchange delay.
| Endpoint | Freshness | Notes |
|----------------------------|-------------|------------------------------|
| `/v1/stocks/quotes/` | 15m delayed | |
| `/v1/stocks/candles/` | 15m delayed | |
| `/v1/stocks/bulkcandles/` | 15m delayed | |
| `/v1/stocks/prices/` | Real-time | |
| `/v1/options/quotes/` | Real-time | OPRA entitlement |
| `/v1/options/chain/` | Real-time | OPRA entitlement |
| `/v1/options/expirations/` | Real-time | Contract metadata |
| `/v1/options/lookup/` | Real-time | Contract metadata |
| `/v1/markets/status/` | Real-time | Calendar metadata |
| `/v1/funds/*` | See note | Pending product confirmation |
### Quant
Same freshness profile as Trader.
| Endpoint | Freshness | Notes |
|----------------------------|-------------|------------------------------|
| `/v1/stocks/quotes/` | 15m delayed | |
| `/v1/stocks/candles/` | 15m delayed | |
| `/v1/stocks/bulkcandles/` | 15m delayed | |
| `/v1/stocks/prices/` | Real-time | |
| `/v1/options/quotes/` | Real-time | OPRA entitlement |
| `/v1/options/chain/` | Real-time | OPRA entitlement |
| `/v1/options/expirations/` | Real-time | Contract metadata |
| `/v1/options/lookup/` | Real-time | Contract metadata |
| `/v1/markets/status/` | Real-time | Calendar metadata |
| `/v1/funds/*` | See note | Pending product confirmation |
### Prime
Same freshness profile as Trader and Quant.
| Endpoint | Freshness | Notes |
|----------------------------|-------------|------------------------------|
| `/v1/stocks/quotes/` | 15m delayed | |
| `/v1/stocks/candles/` | 15m delayed | |
| `/v1/stocks/bulkcandles/` | 15m delayed | |
| `/v1/stocks/prices/` | Real-time | |
| `/v1/options/quotes/` | Real-time | OPRA entitlement |
| `/v1/options/chain/` | Real-time | OPRA entitlement |
| `/v1/options/expirations/` | Real-time | Contract metadata |
| `/v1/options/lookup/` | Real-time | Contract metadata |
| `/v1/markets/status/` | Real-time | Calendar metadata |
| `/v1/funds/*` | See note | Pending product confirmation |
## Notes
- **Real-time data and professional status:** Real-time exchange data is only available to non-professional subscribers. Professional subscribers **never** receive Real-time exchange data, no matter which exchange agreements they sign. Signing an agreement does not change this, and no self-service plan changes it. Delayed / historical data access remains available, depending on the data policies of each exchange. Real-time exchange data for professional use requires direct exchange licensing — [contact our sales team](https://www.marketdata.app/contact/) to discuss it. See [Professional Status Policy](https://www.marketdata.app/docs/docs/account/data-policies/professional-status/), [Exchange Entitlements](https://www.marketdata.app/docs/account/entitlements), and [Professional Status Explained](https://www.marketdata.app/education/stocks/professional-status-explained/).
- **`/v1/funds/*` freshness** is documented per fund-data type and is pending publication here. Until then, refer to the individual endpoint pages under [Funds API](https://www.marketdata.app/docs/api/funds/).
- **Stock quotes and candles carry the 15-minute exchange delay.** `/v1/stocks/quotes/`, `/v1/stocks/candles/`, and `/v1/stocks/bulkcandles/` are delayed 15 minutes on every plan under the [UTP entitlement](https://www.marketdata.app/docs/account/entitlements#utp-entitlement). The delay comes from the exchange, so `mode=live` does not shorten it — see [Data Mode](https://www.marketdata.app/docs/api/universal-parameters/mode).
---
# Data Policies
Source: https://www.marketdata.app/docs/account/data-policies/index.md
Market Data distributes financial data under agreements with stock and options exchanges. These policies explain the obligations those agreements impose on subscribers and how they affect your access to data.
## Policies
- [Exchange Agreements](https://www.marketdata.app/docs/docs/account/data-policies/exchange-agreements/) — The subscriber agreements for UTP, OPRA, and IEX that govern your use of exchange data
- [Free Accounts](https://www.marketdata.app/docs/docs/account/data-policies/free-accounts/) — Why free and trial accounts are limited to 24-hour delayed data
- [Single IP Address](https://www.marketdata.app/docs/docs/account/data-policies/single-ip/) — Why each account may only connect from one IP address at a time
- [Professional Status](https://www.marketdata.app/docs/docs/account/data-policies/professional-status/) — Self-service plans are for non-professional subscribers only; certification and misclassification obligations
- [Data Redistribution](https://www.marketdata.app/docs/docs/account/data-policies/data-redistribution/) — What you may and may not do with the data you receive, and how to obtain redistribution rights
- [Account Verification](https://www.marketdata.app/docs/docs/account/data-policies/account-verification/) — Market Data's policy of verifying all paid subscriber identities and employment for exchange classification
---
# Account Verification Policy
Source: https://www.marketdata.app/docs/account/data-policies/account-verification/index.md
Market Data verifies the identity and employment information of all paid subscribers. This is a requirement of the exchanges whose data we distribute: before we can activate real-time data on an account, we must classify the subscriber as either a professional or non-professional user and certify that classification to the exchange.
See [Professional Status Policy](https://www.marketdata.app/docs/docs/account/data-policies/professional-status/) for details on what the classifications mean and the consequences of misclassification.
Verification activates real-time data only for subscribers classified as non-professional. Professional subscribers never receive real-time exchange data on a self-service plan, no matter which exchange agreements they sign. Real-time access for professional use requires direct exchange licensing — [contact our sales team](https://www.marketdata.app/contact/) to discuss it.
> [!TIP]
> **[Having trouble with verification?]**
>
> If your account verification is delayed or you've been asked to provide additional documentation, see [Account Troubleshooting](https://www.marketdata.app/docs/docs/account/troubleshooting/) for help with common issues.
## Information We Collect
| Contact Information | Employer Information |
|---------------------|----------------------|
| Name | Company Name |
| Address | Address |
| Email | Job Title |
| Phone Number | LinkedIn Profile URL |
Subscribers employed by banks, insurance companies, or other financial institutions are subject to a more thorough review.
## Verification Process
After sign-up, we verify that the information you provided matches third-party databases, including LinkedIn. We also review FINRA BrokerCheck to determine whether you hold any active securities licenses. The outcome of this review determines your professional or non-professional classification.
Profiles with missing or incomplete information cannot be classified. Your account will not be activated for real-time data until verification is complete.
### Missing or Mismatching Information
All fields are required and must match third-party databases. If your profile contains missing, incomplete, or mismatched information, we will request additional documentation — such as a CV, employment letter, or other materials that clarify your job function.
### LinkedIn Profiles
A LinkedIn profile is not required, but it is the easiest way for us to verify your employment information because it is a widely used, publicly accessible professional database. If you do not have a LinkedIn profile or prefer not to share it, just write `https://www.linkedin.com` in the form field and you can provide any of the following as an alternative:
- A current resume or CV
- A photo of your business card showing your current job title and employer
- A recent paystub (please redact any sensitive financial data)
- Any other document that confirms your current employer and role
### Subscribers Who Are Retired or Not Currently Working
If you are retired or not currently working, you must provide work history that clearly documents your most recent position. Your LinkedIn profile, resume, or CV must show all of the following:
- **Employer** — the name of the company or organization
- **Job title** — the role you held
- **End date** — the month and year your most recent position ended
We cannot accept a profile or resume that only lists a job title or employer without a clear end date, as this does not allow us to confirm that you are no longer employed. If your LinkedIn profile does not show this information, please provide a resume or CV that does.
This helps us confirm that you are not currently employed in a role that would qualify as professional use under exchange rules. See [Professional Status Policy](https://www.marketdata.app/docs/docs/account/data-policies/professional-status/) for details on how professional status is determined.
### Self-Employed and Business Owners
If you are self-employed or own a business, your professional status will depend on the nature of the business itself — specifically, whether the business has a need for market data as part of its products or services. This triggers an additional verification step beyond what is required for employed subscribers.
You must provide at least one document that confirms what your business does. Acceptable documentation includes:
- A company LinkedIn page
- A business website
- A brochure or marketing materials describing your products or services
- A business registration or filing that describes the nature of the business
A self-reported business name and description alone is not sufficient. We need independent documentation that confirms the business is not involved in financial services, trading, or other activities that would classify you as a professional user under exchange rules. See [Professional Status Policy](https://www.marketdata.app/docs/docs/account/data-policies/professional-status/) for details.
### Verification Timeframe
We will complete verification within 1 business day of you providing the required information.
## Annual Review
Once per year, we will ask you to confirm that your information is still accurate. You can do this by logging into your account and responding to the prompt that appears after one year has passed since your last verification.
If your employment or professional registration has changed in a way that affects your classification, you are required to update your information and notify us. See [Professional Status Policy](https://www.marketdata.app/docs/docs/account/data-policies/professional-status/) for more details.
## Privacy
The information we collect is used solely to verify your account and comply with exchange regulations. We do not sell your information to third parties. We are required to share it with the stock and options exchanges, who review it periodically for accuracy.
---
# Data Redistribution Policy
Source: https://www.marketdata.app/docs/account/data-policies/data-redistribution/index.md
Market Data's self-service plans are personal licenses that allow you to access and use market data for your own purposes only. Redistribution of that data — in any form — is not permitted under these plans.
## What Counts as Redistribution
Redistribution includes any method of making Market Data's data available to others, including:
- Selling, reselling, or retransmitting data to third parties
- Exposing data through your own API or data feed
- Distributing data in bulk export files shared with others
- Sharing API access or account credentials with other users
- Embedding live or recent data in a product or service accessible to others
- Sharing a spreadsheet or workspace that pulls live data from Market Data with collaborators
The single-IP restriction is one enforcement mechanism for this policy. See [Single IP Address Policy](https://www.marketdata.app/docs/docs/account/data-policies/single-ip/) for details.
## Commercial Redistribution
To display market data to the users of your own app, dashboard, or product, use the [Commercial Plan](https://www.marketdata.app/docs/docs/account/plans/commercial/). It includes a business-wide commercial license with display rights, and there are no exchange agreements to sign, no exchange fees, and no per-user fees.
For dedicated exchange feeds, API distribution to other businesses, or resale rights, [contact us](https://www.marketdata.app/contact/) about enterprise licensing.
## Violations
Redistribution of market data without a commercial license is a breach of your exchange agreements and Market Data's [Terms of Service](https://www.marketdata.app/terms/), and will result in immediate account termination.
---
# Exchange Agreements
Source: https://www.marketdata.app/docs/account/data-policies/exchange-agreements/index.md
Market Data is an authorized vendor for the exchanges listed below. When you access market data through Market Data's API or other services, you agree to the subscriber agreement of each exchange whose data you receive. Your use of market data through Market Data's services is subject to Market Data's [Terms of Service](https://www.marketdata.app/terms/) and the applicable exchange agreements below.
## UTP Plan
The **Unlisted Trading Privileges (UTP) Plan** provides consolidated stock market data for NASDAQ-listed securities, including quotes, last sale prices, and trade data.
[UTP Plan Subscriber Agreement](pathname:///docs/exchange-agreements/utp-subscriber-agreement.pdf)
## OPRA
The **Options Price Reporting Authority (OPRA)** is the exclusive provider of consolidated options market data in the U.S., including last sale prices and current quotations for all listed options.
[OPRA Subscriber Agreement](pathname:///docs/exchange-agreements/opra-subscriber-agreement.pdf)
## IEX
The **Investors Exchange (IEX)** is a registered national securities exchange providing real-time stock quotes and trade data.
[IEX Controlled Data Recipient Agreement](pathname:///docs/exchange-agreements/iex-controlled-data-recipient-agreement.pdf)
---
# Free and Trial Account Data Policy
Source: https://www.marketdata.app/docs/account/data-policies/free-accounts/index.md
Free Forever and trial accounts are limited to data that is at least 24 hours old. This restriction arises from two independent requirements: exchange rules on user identification and exchange rules on free trials.
## User Identification Requirement
Before an exchange permits a vendor to distribute real-time or intraday delayed data to a subscriber, the subscriber must be identified and their professional status determined. Free and trial account holders have not completed this process.
Market Data is required by the exchanges to collect, verify, and report the identity of all subscribers who receive real-time or intraday data. Until your account is verified and you have signed the required [exchange agreements](https://www.marketdata.app/docs/docs/account/data-policies/exchange-agreements/), Market Data can only provide data that is at least 24 hours old, which is not subject to the same identification requirements.
See [Account Compliance](https://www.marketdata.app/docs/docs/account/data-policies/account-verification/) for details on the verification process.
## Exchange Restrictions on Free Trials
Separately from identification requirements, the exchanges place limits on which subscribers may receive real-time or intraday data at no charge:
- **Non-professional subscribers** are not permitted to receive real-time or intraday data through a free trial under any circumstances.
- **Professional subscribers** may be eligible for limited free trial access from some exchanges, but this is not universally permitted across all exchanges.
Because Market Data serves a broad base of non-professional subscribers, and because free trial access to real-time or intraday data is prohibited for non-professionals regardless of plan, all free and trial accounts are limited to 24-hour delayed data.
## Accessing Real-Time and Intraday Data
To access real-time or 15-minute delayed data, all of the following are required:
1. **A paid subscription** — real-time and intraday data are not available on free or trial plans.
2. **Account verification** — your identity and professional status must be confirmed by Market Data before exchange data can be enabled. See [Account Compliance](https://www.marketdata.app/docs/docs/account/data-policies/account-verification/).
3. **Non-professional classification** — professional subscribers never receive real-time exchange data on a self-service plan, no matter which exchange agreements they sign. Real-time access for professional use requires direct exchange licensing — [contact our sales team](https://www.marketdata.app/contact/) to discuss it. See [Professional Status Policy](https://www.marketdata.app/docs/docs/account/data-policies/professional-status/).
4. **Signed exchange agreements** — you must sign the subscriber agreement for each exchange whose data you wish to receive. See [Exchange Entitlements](https://www.marketdata.app/docs/docs/account/entitlements/).
---
# Professional Status Policy
Source: https://www.marketdata.app/docs/account/data-policies/professional-status/index.md
All Market Data self-service plans are available to **non-professional subscribers only**. Exchange rules require vendors to classify and certify the status of every subscriber who receives real-time market data. It is your responsibility to subscribe under the correct classification. Business use of market data is only available under the [Commercial Plan](https://www.marketdata.app/docs/docs/account/plans/commercial/), which includes real-time stock and options prices with no exchange licensing and no exchange fees.
For a full explanation of how the exchanges define professional status and how to determine your own classification, see [Professional Status Explained](https://www.marketdata.app/education/stocks/professional-status-explained/).
## Professional Subscribers and Real-Time Data
Professional subscribers never receive real-time exchange data on a self-service plan, no matter which exchange agreements they sign. Signing an agreement does not change this, and neither does upgrading your plan. Delayed / historical data access remains available, depending on the data policies of each exchange.
Real-time exchange data for professional use requires **direct exchange licensing**, which Market Data arranges individually. [Contact our sales team](https://www.marketdata.app/contact/) to discuss exchange licensing for your use case. All real-time data requests from professional subscribers go through the sales team.
## Self-Certification Requirement
After purchasing a plan, all subscribers must complete a **Data Use Interview** — a self-certification process in which you confirm that your use of market data meets the non-professional criteria set by the exchanges. Your account cannot be fully activated for real-time data until this process is complete.
Non-professional certifications must be renewed once per year. You will be prompted to re-certify annually to confirm your status has not changed.
## Obligation to Update Your Status
If your employment, registration, or data use changes in a way that affects your classification, you are required to notify Market Data. Continued use of a non-professional plan after becoming a professional user is a breach of your exchange agreements and this policy.
## Consequences of Misclassification
If Market Data determines that a subscriber has incorrectly self-certified as non-professional:
- The account will be reclassified as professional
- Exchange fees will be assessed retroactively from the date of the incorrect certification
- The account may be suspended or terminated without refund
- Market Data reserves the right to pursue all remedies available under the [Terms of Service](https://www.marketdata.app/terms/)
Refunds are not available on the basis of unexpected classification outcomes. All classifications are made according to exchange rules based on the information you provide.
## Unverified Accounts
If Market Data is unable to confirm your non-professional status — for example, because your profile is incomplete or your information cannot be verified — your account will be restricted to delayed and historical data until verification is complete. See [Account Compliance](https://www.marketdata.app/docs/docs/account/data-policies/account-verification/) for details on the verification process.
---
# Single IP Address Policy
Source: https://www.marketdata.app/docs/account/data-policies/single-ip/index.md
Each Market Data account may only access the API from one IP address at a time. This policy is a direct requirement of the exchange agreements under which Market Data distributes market data.
## Why This Policy Exists
The exchanges that license their data to Market Data prohibit unauthorized redistribution. When you sign an [exchange agreement](https://www.marketdata.app/docs/docs/account/data-policies/exchange-agreements/), you agree to use the data solely for your own personal or business purposes and not to share or redistribute it to others.
Allowing a single account to be used from multiple IP addresses simultaneously would make it impossible to enforce this restriction, because it would allow one subscriber to feed data to multiple people or systems at once. The single IP policy is how Market Data enforces the exchange's per-subscriber distribution rules.
## What Is and Is Not Permitted
**Permitted:**
- Switching from one device or IP address to another (e.g., moving from your laptop to a cloud server, or reconnecting after your IP changes)
- Using a VPN, provided requests consistently originate from a single IP at any given time
**Not permitted:**
- Connecting simultaneously from more than one IP address
- Sharing your API token with other users
- Redistributing market data received through your account to any third party
## How the Restriction Is Enforced
Market Data monitors for patterns that indicate simultaneous access from multiple IP addresses. A single IP change is permitted. However, if requests alternate back and forth between two IP addresses within a five-minute window — a pattern consistent with two active devices — your account is temporarily blocked for 5 minutes.
If your account has been blocked, see [403: Multiple IP Addresses](https://www.marketdata.app/docs/docs/api/troubleshooting/multiple-ip-addresses/) for resolution steps.
## Commercial Use and Multi-Device Access
The single IP policy applies to all self-service plans. The [Commercial Plan](https://www.marketdata.app/docs/docs/account/plans/commercial/) has no IP limit: if your use case requires API access from multiple IP addresses simultaneously — for example, distributed systems or serving data to multiple users — the Commercial Plan lets you call the API from as many servers and IP addresses as your product needs. For needs beyond the Commercial Plan, [contact us](https://www.marketdata.app/contact/) about enterprise licensing.
## Violations
Attempting to circumvent this policy by sharing accounts or redistributing data will result in permanent account suspension.
---
# Exchange Entitlements
Source: https://www.marketdata.app/docs/account/entitlements/index.md
Exchange entitlements are permissions granted by exchanges that allow Market Data to distribute their data to you. Each entitlement requires you to sign the relevant exchange agreement and adhere to the exchange's data usage policies.
For your obligations under these agreements, see [Exchange Agreements](https://www.marketdata.app/docs/docs/account/data-policies/exchange-agreements/). For restrictions on how the data may be used, see [Data Redistribution](https://www.marketdata.app/docs/docs/account/data-policies/data-redistribution/).
## Available Entitlements
### IEX Entitlement
- **Data Type**: Stock quotes and trades
- **Exchange**: Investors Exchange (IEX)
- **Benefit**: Access to stock quote and trade data
- **Plan Requirement**: Any paid Market Data plan
### UTP Entitlement
- **Data Type**: Consolidated UTP data (candlesticks, quotes and trades)
- **Exchange**: Unlisted Trading Privileges (UTP) — includes NASDAQ, NYSE, and other major exchanges
- **Benefit**: Access to 15-minute delayed intraday stock candles from major exchanges
- **Plan Requirement**: Any paid Market Data plan
### OPRA Entitlement
- **Data Type**: Options quotes and trades
- **Exchange**: Options Price Reporting Authority (OPRA)
- **Benefit**: Access to options data
- **Plan Requirement**: Paid Trader plan or above
## How to Get Your Entitlement
1. **Log into your account** at [Market Data Dashboard](https://dashboard.marketdata.app/marketdata/)
2. **Navigate to the Profile section**
3. **Review and sign** the required agreements for the data you need
4. **Wait for approval** (typically processed within 1 business day)
---
# Feature Requests
Source: https://www.marketdata.app/docs/account/feature-requests/index.md
Market Data is _extremely_ customer-focused and we want our products and services to satisfy our users. Please make sure to visit our [Product Roadmap](https://roadmap.marketdata.app/) to see what we have planned along with our [Feature Request Board](https://roadmap.marketdata.app/features) to submit or upvote your own ideas.
## Feature Request Procedure
Any user can make a feature request with their Market Data account. Just visit the [Feature Request Board](https://roadmap.marketdata.app/features) and add your requested feature. If enough people upvote it, we'll consider adding your feature to a future version of Market Data.
## Urgent Feature Requests
If you urgently need a specific feature and are willing to pay to have it implemented immediately, please let us know. For features that would demand less than 2 days of developer time, we can normally accommodate these requests if you subscribe to a yearly Trader plan (at a cost of $360.00) or extend an existing membership by another year. If you are hesitating to sign-up to Market Data because you need a specific feature that we do not currently offer, we encourage you to contact us to see if we can implement your feature request as an urgent feature.
### Urgent Feature Guarantee
If we agree to your urgent feature request in return for a new subscription or an upgrade of an existing subscription, we'll implement your feature request within 14 days. If we fail to meet the deadline, your Market Data account will be free until we deploy the feature you've paid for.
---
# Free Accounts
Source: https://www.marketdata.app/docs/account/free-accounts/index.md
Market Data offers two ways for users to use our service for free. We offer the "Free Forever" plan, which allows for 100 API requests per day. We also offer a free 30 day trial (no credit card required) so that users can test our services and determine which plan best meets their needs. Trial accounts work almost exactly the same as paid accounts and have the same [plan limits](https://www.marketdata.app/docs/account/plan-limits). There are a few differences, however.
## Comparison Table
| Feature | Free Forever | Trial Plans | Paid Plans |
|------------------------|--------------|-------------|-------------|
| 24 Hour Delayed Data | ✅ | ✅ | ❌ |
| 15 Minute Delayed Data | ❌ | ❌ | ✅ |
| Real-Time Data\* | ❌ | ❌ | ✅ |
| Standard Endpoints | ✅ | ✅ | ✅ |
| Premium Endpoints | ❌ | AAPL Only | ✅ |
| Historical Data | 1 Year | 1 Year | Full Access |
\* For endpoint-level freshness rules, see [Data Freshness](https://www.marketdata.app/docs/docs/account/data-freshness/).
## 24 Hour Delayed Data
Free Forever and trial accounts only have access to data that is delayed by at least 24 hours. Check the `date` column in the Add-on or the `updated` key in the API's JSON response to get the exact date and time of the quote data you've received. Real-time data is only available with paid subscription plans.
For the reasons behind this restriction, see the [Free Accounts Data Policy](https://www.marketdata.app/docs/docs/account/data-policies/free-accounts/).
## Standard vs Premium Endpoints
Free and trial plans have access to most standard endpoints that give pricing information. However, our premium endpoints that provide fundamental data (such as earnings data) are only accessible with a paid subscription.
## Historical Data
Free and trial plans cannot access data older than 1 year. We welcome you to use our free trial to test our historical data going back one year. However, if you need older data, a subscription is required.
---
# Invoices
Source: https://www.marketdata.app/docs/account/invoices/index.md
There are two ways to access your invoices: through the order confirmation email you received at the time of purchase, or through the Billing Portal.
## Download From Your Email
When you made your purchase, an order confirmation email was sent to the email address associated with your order. This email includes your order details, subscription information, and a **Download invoice** link that lets you download a PDF invoice immediately — no account or login required.
To find the email, search your inbox for **"Market Data" "order invoice"**. The email will include details such as:
- The plan you purchased and the amount charged
- Your subscription renewal date and billing cycle
- A **Download invoice** link
> [!TIP]
> If you can't find the email, check your spam or junk folder. The confirmation email is sent by our billing partner, not directly from Market Data, so it may be filtered.
## Download From the Billing Portal
You can also generate and download invoices for any of your orders by logging in to the [Billing Portal](https://cc.payproglobal.com/Customer/Account/Login).
1. Log in to the [Billing Portal](https://cc.payproglobal.com/Customer/Account/Login).
2. Navigate to your order history.
3. Select the order you need an invoice for.
4. Download the PDF invoice.
If this is your first time accessing the Billing Portal, see [Billing Portal](https://www.marketdata.app/docs/docs/account/billing-portal#how-to-access-the-billing-portal-for-the-first-time) for instructions on setting up your password.
---
# Plan Limits
Source: https://www.marketdata.app/docs/account/plan-limits/index.md
Each Market Data plan comes with certain limitations to allow for shared use of our servers. We offer several plans with different price points depending on the quantity of data needed.
## Standard Plans
| | Free Forever | Starter | Trader | Quant | Prime Plans |
|--------------------|--------------|-------------------|-------------|------------------|------------------|
| Daily Credits | 100 | 10,000 | 100,000 | No Limit | No Limit |
| Per-Minute Credits | No Limit | No Limit | No Limit | 10,000 | 100,000 |
| Historical Data | 1 Year | 5 Years | No Limit | No Limit | No Limit |
| Stocks Data Type | Delayed | Real-time\* | Real-time\* | Real-time\* | Real-time\* |
| Options Data Type | Delayed | 15-minute delayed | Real-time | Real-time | Real-time |
| API Endpoints | Standard | Premium | Premium | Premium + Custom | Premium + Custom |
\* See [Data Freshness](https://www.marketdata.app/docs/account/data-freshness) for endpoint-level freshness.
## Credits
Each time you call the API, the system increases your credits counter. Normally each successful response consumes 1 credit. However, **if you request multiple symbols in a single API call using `stocks/quotes`, `stocks/prices`, `stocks/bulkcandles`, or `options/chain`, each symbol included in the response consumes credits**.
> [!CAUTION]
> For users working with options, take care before repeatedly requesting quotes for an entire option chain. **Each option symbol included in the response consumes credits**. If you download the entire SPX option chain (which has 20,000+ option symbols), you can exhaust your credit limit very quickly. Use our option chain filters to request only the strikes and expirations you need.
> [!NOTE]
> **[Trial plans]**
>
> Starter Trial, Trader Trial, and Prime Trial do not support `mode=cached`.
## Device Restrictions
All plans are subject to a single IP address limit. Each account may only connect from one IP address at a time. Multiple simultaneous connections, account sharing, and data redistribution are not permitted.
See the [Single IP Address Policy](https://www.marketdata.app/docs/docs/account/data-policies/single-ip/) for details.
### Throttling
All plans are subject to a maximum of 50 simultaneous requests at any given time.
## Historical Data Age Limit
Free Forever accounts can access up to 1 year of historical data and Starter accounts can access up to 5 years. Trader, Quant, and Prime plans have no historical data age limit. If you request data older than your plan allows, the request will be denied.
## Data Type
- Free Forever provides delayed stocks/options data.
- Starter provides real-time stocks\* and 15-minute delayed options data.
- Trader, Quant, and Prime provide real-time stocks\*/options data.
> [!NOTE]
> Free trials of paid plans provide delayed data. Real-time data is only available with paid versions of the Trader plan and above.
For endpoint-level freshness rules and the Delayed → Historical rollover timing — including why options data only rolls at 9:30 AM ET the next trading day rather than at the prior session's close — see [Data Freshness](https://www.marketdata.app/docs/account/data-freshness).
## API Endpoints
The Free Forever plan only provides access to pricing data. Premium endpoints and spreadsheet formulas that contain reference data are not available on free plans. Quant and Prime plans also have access to custom-built endpoints to satisfy the needs of their specific application.
## About Usage Windows
- Free Forever, Starter, and Trader use daily windows that reset at 9:30 AM Eastern Time (NYSE opening bell).
- Quant and Prime use per-minute credit windows.
- Any single API request is permitted as long as you have at least 1 credit remaining in the current window, even if the request consumes more credits than you have available. After the request, your balance may go negative and further requests will be blocked until the window resets.
---
# Data Plans
Source: https://www.marketdata.app/docs/account/plans/index.md
Market Data provides a variety of data subscriptions for users who need on-going access to Market Data. Explore the details of all our plans and decide which one is right for you.
## Explore our Plans
---
# Commercial Plan
Source: https://www.marketdata.app/docs/account/plans/commercial/index.md
The **Commercial Plan** gives your business real-time, delayed, historical, and tick-level market data for stocks and options, together with a commercial license to display it to your users — all for one price, starting at **$333/month**. There are no exchange agreements to sign, no exchange fees, and no per-user fees.
## Pricing
**$333/month to start, billed quarterly.** The price steps up twice during the first year, then stays fixed:
| Months | Monthly Price | Billed Each Quarter |
|-----------|---------------|---------------------|
| 1–6 | $333/month | $999 |
| 7–12 | $666/month | $1,998 |
| 13 and on | $999/month | $2,997 |
The price includes the commercial license. There are no exchange fees and no per-user fees on top of it, however many users your product serves.
Every Commercial Plan starts with a **14-day free trial**, with no charge until the trial ends, and there is no long-term contract. [Contact us](https://www.marketdata.app/contact/) to get started.
## No Exchange Licensing, No Extra Fees
A business that shows market data to its users usually has to sign agreements with each exchange, pay monthly exchange fees, and pay again for every user who sees the data. Those costs grow with every user you add, and the paperwork comes before you can launch.
The Commercial Plan removes all of it. Real-time prices, delayed exchange data, historical data, and tick-level data all come under one license, at one price. You sign nothing with the exchanges, you pay no exchange fees, and you pay nothing more as your user base grows — so your data costs stay the same whether your product has ten users or ten thousand.
## What You Get with the Commercial Plan
### Data
- **Real-Time Stock Prices**: Real-time derived prices that closely track exchange prices, with last price, open, close, volume, and pre-market and post-market volume.
- **Real-Time Options Prices**: Real-time derived options prices, with implied volatility and the greeks — delta, gamma, theta, and vega.
- **WebSocket Streaming**: Stream real-time stock and options prices over WebSocket, or request them over the REST API.
- **Tick-Level Market Data**: Market data down to the individual tick.
- **15-Minute Delayed CBOE Data**: Exchange data from CBOE on a 15-minute delay.
- **End-of-Day and Historical Data**: End-of-day and historical data for stocks and options, for backtesting, analytics, and historical charts.
- **US Fundamentals**: Fundamentals data for US companies.
### Rights
- **Commercial Use and Display Rights**: A business-wide commercial license. Use the data across your business and display it to the users of your app, dashboard, or product.
- **No Exchange Licensing**: No exchange agreements to sign and no exchange paperwork.
- **No Exchange Fees or Per-User Fees**: One price, however many users see the data.
- **No IP Limit**: Call the API from as many servers and IP addresses as your product needs.
### Tools and Limits
- **AI Assistant Access**: Connect Claude, ChatGPT, and other AI assistants to the data through an MCP server.
- **Rate Limits**: 2,000 API calls per minute, 1 request per minute for bulk downloads and paging, and 3 WebSocket connections.
## What You Can Build
The Commercial Plan is made for businesses bringing a product to market on top of market data:
- **Stock and options apps** that display real-time and historical data to paying users
- **SaaS and fintech products** built on top of the data
- **Financial dashboards** distributed to clients or employees
- **Research products and newsletters** that publish data-driven content
- **Charting and analytics tools** built on real-time, historical, and tick-level data
- **Internal business analytics** across your whole company
## Comparison with Other Plans
| Feature | Starter | Trader | Prime | **Commercial** |
|--------------------------------|---------|--------|---------|----------------------|
| Price | $30/mo | $75/mo | $250/mo | **$333/mo to start** |
| Commercial Use | ❌ | ❌ | ❌ | ✅ |
| Public Display to Your Users | ❌ | ❌ | ❌ | ✅ |
| Redistribution in Your Product | ❌ | ❌ | ❌ | ✅ |
| WebSocket Streaming | ❌ | ❌ | ❌ | ✅ |
| Tick-Level Market Data | ❌ | ❌ | ❌ | ✅ |
| Multiple IP Addresses | ❌ | ❌ | ❌ | ✅ |
## Why Choose the Commercial Plan?
The Commercial Plan is the simplest way to launch a product on market data. One price covers real-time stock and options prices over REST and WebSocket, tick-level, delayed, and historical data, and the commercial license to display all of it — with no exchange agreements, no exchange fees, and no per-user fees as you grow. It starts at **$333/month, billed quarterly**, with a 14-day free trial.
[Contact us](https://www.marketdata.app/contact/) to get started or to discuss your specific use case.
## Need More?
For dedicated exchange feeds, API distribution to other businesses, or resale rights, [contact us](https://www.marketdata.app/contact/) about enterprise licensing.
---
# Free Forever Plan
Source: https://www.marketdata.app/docs/account/plans/free-forever/index.md
The **Free Forever Plan** is designed for users who want to explore Market Data's capabilities without any cost. It's perfect for individuals or small projects needing basic access to financial data. Here's everything you need to know about what the Free Forever plan offers and its limitations.
## Pricing
**Free** — $0/month, no credit card required.
## What You Get with Free Forever
- **100 API Credits per Day**: Ideal for small-scale applications or personal projects.
- **24-Hour Delayed Data**: Access to data that is delayed by at least 24 hours.
- **Standard Endpoints**: Use most of our standard endpoints that offer pricing information, allowing you to integrate financial data into your applications.
- **1 Year of Historical Data**: Explore and utilize historical data up to one year back for your analysis and projects.
## Limitations of the Free Forever Plan
While the Free Forever plan offers a great starting point, there are some limitations to be aware of:
- **No 15-Minute Delayed Data**: Access to 15-minute delayed financial data is not available under this plan.
- **No Real-Time Data**: Access to real-time financial data is not available under this plan.
- **No Premium Endpoints**: Fundamental data, such as earnings data provided through our premium endpoints, are not accessible.
- **Historical Data Limited to 1 Year**: If your project requires data older than one year, you would need to consider upgrading to a paid subscription.
## Comparison
To help you better understand the differences between our plans, here's a quick comparison:
| Feature | Free Forever | Paid Plans |
|------------------------|--------------|-------------|
| Historical Data | 1 Year | Full Access |
| 15-Minute Delayed Data | ❌ | ✅ |
| Real-Time Data\* | ❌ | ✅ |
| Standard Endpoints | ✅ | ✅ |
| Premium Endpoints | ❌ | ✅ |
\* For endpoint-level freshness rules and the Delayed → Historical rollover timing (especially for options, where data only rolls at 9:30 AM ET the next trading day), see [Data Freshness](../data-freshness.md).
## Why Choose Free Forever?
The Free Forever plan is an excellent choice for those new to Market Data or for projects that are in the early stages of development. It allows you to test our services and understand how our data can enhance your projects without any financial commitment. Plus, you can upgrade to a paid plan anytime as your needs grow.
Start exploring the financial world with Market Data's Free Forever plan today and unlock the potential of financial data for your projects!
---
# Prime Plan
Source: https://www.marketdata.app/docs/account/plans/prime/index.md
The **Prime Plan** is Market Data's most robust offering, designed for high-powered traders and investors who require extensive data access and customization options. This plan is tailored to meet the needs of sophisticated trading operations, offering unparalleled data access with the flexibility to handle massive volumes of API requests. Here's a comprehensive overview of the Prime Plan.
## Pricing
**$250/month** — month-to-month billing only. The Prime Plan does not offer an annual billing option.
## What You Get with the Prime Plan
- **No Daily Limit on API Credits**: Enjoy unrestricted access to our API, designed to support the most demanding data needs without the worry of hitting daily limits.
- **100,000 Credits Per Minute**: While there's no daily limit, the plan is subject to a generous per-minute rate limit to ensure optimal performance for all users.
- **Real-Time Data\***: Access up-to-the-second financial data, ensuring your trading decisions are based on the most current market information. Note that real-time data is only available to users who qualify as non-professionals. [Learn more about professional status requirements](https://www.marketdata.app/education/stocks/professional-status-explained/).
- **Premium + Custom API Endpoints**: Not only do you get access to all premium endpoints, but we also offer the possibility of custom-built endpoints to satisfy your specific trading requirements.
- **Unlimited Historical Data**: There are no restrictions on the age of historical data you can access, making it ideal for deep historical analysis and backtesting.
## Features of the Prime Plan
The Prime Plan is designed with the highest level of access and customization in mind:
- **Customization and Flexibility**: Tailor the plan to fit your specific trading needs, including custom API endpoints developed in collaboration with our team.
- **Optimized for High Volume**: Handle massive data volumes with a high per-minute rate limit, ensuring your trading operations run smoothly without interruption.
- **Comprehensive Data Access**: From real-time data to unlimited historical data, get everything you need for analysis, trading, and decision-making.
## Comparison with Other Plans
Here's how the Prime Plan compares to our other offerings:
| Feature | Free Forever | Starter | Trader | Quant | Prime |
|-----------------------|--------------|------------|-------------|-------------|-------------|
| Daily API Credits | 100/day | 10,000/day | 100,000/day | Unlimited | Unlimited |
| Per Minute Rate Limit | No Limit | No Limit | No Limit | 10,000 | 100,000 |
| Real-Time Data\* | ❌ | ✅ | ✅ | ✅ | ✅ |
| Premium Endpoints | ❌ | ✅ | ✅ | ✅ + Custom | ✅ + Custom |
| Historical Data | 1 Year | 5 Years | Full Access | Full Access | Full Access |
\* For endpoint-level freshness rules, see [Data Freshness](../data-freshness.md).
## Why Choose the Prime Plan?
The Prime Plan is the ultimate choice for high-powered traders and investors looking for the most comprehensive financial data solution. With its no-limit approach to data access, real-time information, and the ability to customize features, it provides the tools necessary for sophisticated analysis, trading, and decision-making at scale.
Embrace the full potential of Market Data with the Prime Plan and elevate your trading capabilities to new heights.
---
# Quant Plan
Source: https://www.marketdata.app/docs/account/plans/quant/index.md
The **Quant Plan** is Market Data's offering designed for high-powered traders and investors who require extensive data access and customization options. This plan is tailored to meet the needs of sophisticated trading operations, offering unparalleled data access with the flexibility to handle massive volumes of API requests. Here's a comprehensive overview of the Quant Plan.
## Pricing
**$125/month** — month-to-month billing only. The Quant Plan does not offer an annual billing option.
## What You Get with the Quant Plan
- **No Daily Limit on API Credits**: Enjoy unrestricted access to our API, designed to support the most demanding data needs without the worry of hitting daily limits.
- **10,000 Credits Per Minute**: While there's no daily limit, the plan is subject to a generous per-minute rate limit to ensure optimal performance for all users.
- **Real-Time Data\***: Access up-to-the-second financial data, ensuring your trading decisions are based on the most current market information. Note that real-time data is only available to users who qualify as non-professionals. [Learn more about professional status requirements](https://www.marketdata.app/education/stocks/professional-status-explained/).
- **Premium + Custom API Endpoints**: Not only do you get access to all premium endpoints, but we also offer the possibility of custom-built endpoints to satisfy your specific trading requirements.
- **Unlimited Historical Data**: There are no restrictions on the age of historical data you can access, making it ideal for deep historical analysis and backtesting.
## Features of the Quant Plan
The Quant Plan is designed with the highest level of access and customization in mind:
- **Customization and Flexibility**: Tailor the plan to fit your specific trading needs, including custom API endpoints developed in collaboration with our team.
- **Optimized for High Volume**: Handle massive data volumes with a high per-minute rate limit, ensuring your trading operations run smoothly without interruption.
- **Comprehensive Data Access**: From real-time data to unlimited historical data, get everything you need for analysis, trading, and decision-making.
## Comparison with Other Plans
Here's how the Quant Plan compares to our other offerings:
| Feature | Free Forever | Starter | Trader | Quant | Prime |
|-----------------------|--------------|------------|-------------|-------------|-------------|
| Daily API Credits | 100/day | 10,000/day | 100,000/day | Unlimited | Unlimited |
| Per Minute Rate Limit | No Limit | No Limit | No Limit | 10,000 | 100,000 |
| Real-Time Data\* | ❌ | ✅ | ✅ | ✅ | ✅ |
| Premium Endpoints | ❌ | ✅ | ✅ | ✅ | ✅ |
| Historical Data | 1 Year | 5 Years | Full Access | Full Access | Full Access |
\* For endpoint-level freshness rules, see [Data Freshness](../data-freshness.md).
## Why Choose the Quant Plan?
The Quant Plan is the ultimate choice for high-powered traders and investors looking for the most comprehensive financial data solution. With its no-limit approach to data access, real-time information, and the ability to customize features, it provides the tools necessary for sophisticated analysis, trading, and decision-making at scale.
Embrace the full potential of Market Data with the Quant Plan and elevate your trading capabilities to new heights.
---
# Starter Trial
Source: https://www.marketdata.app/docs/account/plans/starter-trial/index.md
Market Data is excited to offer a **30-day free trial** of our Starter Plan, no credit card required. This trial is designed to let users explore the capabilities of the Starter Plan, ensuring it's the right fit for their needs before committing to a subscription. Here's what you need to know about the Starter Trial Plan.
## Pricing
**$0 for 30 days** — no credit card required. The trial is strictly limited to 30 days and **cannot be renewed or extended**.
Because no credit card is required to start the trial, **you will never be auto-billed when it ends**. If you take no action, your account automatically converts to the [Free Forever Plan](https://www.marketdata.app/docs/account/plans/free-forever) at the end of the 30 days. To keep premium features, you can subscribe to the [Starter Plan](https://www.marketdata.app/docs/account/plans/starter) for $12/month (billed annually) or $30/month at any time.
## What You Get with the Starter Trial Plan
- **10,000 API Credits per Day**: Experience the increased capacity for API requests and see how it supports larger projects.
- **24-Hour Delayed Data for Stocks and Options**: Access to data that is delayed by at least 24 hours for both stocks and options.
- **1 Year Of Historical Data Access**: Explore our extensive historical data archives and fetch data from the last year.
- **Full Historical Data Access (AAPL Ticker)**: Fetch complete historical data from our extensive archives for the AAPL ticker with no date restrictions.
- **Premium Endpoints (AAPL Ticker)**: Explore our premium endpoints using the AAPL ticker, including fundamental data like earnings.
## Features of the Starter Trial Plan
The Starter Trial Plan mirrors the full Starter Plan in terms of features, with a few key differences:
- **Duration**: The trial lasts for 30 days, giving you ample time to test out the features and decide if the plan meets your needs.
- **No Credit Card Required**: Start your trial without any financial commitment, allowing you to explore our services risk-free.
- **Full Access with Limitations**: While you have access to most of the same features as the paid Starter Plan, remember that the trial is designed for evaluation purposes. It is not a full substitute of the actual Starter Plan.
## What You Don't Get With the Starter Trial
- **No Real-Time Stock Data**: Access to real-time stock data is not included in the trial.
- **No 15-Minute Delayed Options Data**: Access to 15-minute delayed options data is not available under this trial.
- **Historical Data Access (All Tickers)**: Historical data access on tickers other than AAPL is limited to 1 year.
- **Premium Endpoints (All Tickers)**: Get full access to our premium endpoints with all tickers.
## Comparison with Other Plans
Here's a quick comparison to help you understand the Starter Trial in relation to our other offerings:
| Feature | Free Forever | Starter Trial | Starter Plan |
|--------------------|--------------|----------------|----------------|
| API Credits | 100 per day | 10,000 per day | 10,000 per day |
| Stock Data Delay | 24 hours | 24 hours | Real-time\* |
| Options Data Delay | 24 hours | 24 hours | 15 minutes |
| Historical Data | 1 Year | 1 Year | 5 Years |
| Standard Endpoints | ✅ | ✅ | ✅ |
| Premium Endpoints | ❌ | AAPL Only | ✅ |
\* For endpoint-level freshness rules and the Delayed → Historical rollover timing (especially for options, where data only rolls at 9:30 AM ET the next trading day), see [Data Freshness](../data-freshness.md).
## Why Take A Trail of the Starter Plan?
The Starter Trial is an excellent opportunity to test the waters and see how the enhanced features can benefit your project. Whether you're scaling up or need more detailed financial data for analysis, this trial provides a risk-free way to explore our services.
Start your journey with Market Data's Starter Trial today and unlock the potential of comprehensive financial data analysis without any upfront commitment.
---
# Starter Plan
Source: https://www.marketdata.app/docs/account/plans/starter/index.md
The **Starter Plan** is tailored for users who require more from their financial data access, with extensive historical records and premium features. It's an ideal choice for growing projects that need deeper insights and more data points. Here's a detailed overview of what the Starter Plan offers.
## Pricing
- **Annual**: $12/month (billed annually at $144/year)
- **Monthly**: $30/month
## What You Get with the Starter Plan
- **10,000 API Credits per Day**: A significant increase in daily API requests to support larger projects.
- **Real-Time Stock Data\***: Access to real-time market data for stocks, ensuring you're working with current information. Note that real-time data is only available to users who qualify as non-professionals. [Learn more about professional status requirements](https://www.marketdata.app/education/stocks/professional-status-explained/).
- **15-Minute Delayed Options Data**: Access to options data with a 15-minute delay.
- **5 Years of Historical Data**: Dive deeper into financial trends with access to data going back five years, ideal for thorough analysis and backtesting.
- **Premium Endpoints**: Unlock our all our premium endpoints, including fundamental data like earnings, for a comprehensive understanding of market dynamics.
## Advantages of the Starter Plan
The Starter Plan comes with several key advantages designed to support your project's growth:
- **Comprehensive Data Access**: With premium endpoints, real-time stock data, and delayed options data, you can make informed decisions based on thorough analysis.
- **Extended Historical Data**: The ability to access 5 years of historical data opens up new possibilities for analysis and insight.
- **Increased API Credits**: The jump to 10,000 API credits per day allows for more extensive data collection and integration.
## Comparison
To give you a clear idea of how the Starter Plan stands against our other offerings, here's a comparison:
| Feature | Free Forever | Starter Plan | Trader Plan |
|--------------------|--------------|----------------|-----------------|
| API Credits | 100 per day | 10,000 per day | 100,000 per day |
| Stock Data Delay | 24 hours | Real-time\* | Real-time\* |
| Options Data Delay | 24 hours | 15 minutes | Real-time |
| Standard Endpoints | ✅ | ✅ | ✅ |
| Premium Endpoints | ❌ | ✅ | ✅ |
| Historical Data | 1 Year | 5 Years | Full Access |
\* For endpoint-level freshness rules and the Delayed → Historical rollover timing (especially for options), see [Data Freshness](../data-freshness.md).
## Why Choose the Starter Plan?
The Starter Plan is perfect for users who have outgrown the Free Forever plan and need more robust data access and capabilities. Whether you're scaling up your project or need more detailed financial data for analysis, the Starter Plan provides the tools and data you need to succeed.
Upgrade to the Starter Plan today and take your financial data analysis to the next level with Market Data!
---
# Trader Trial
Source: https://www.marketdata.app/docs/account/plans/trader-trial/index.md
Market Data is thrilled to introduce a **30-day free trial** of our Trader Plan, no credit card required. This trial is crafted to allow users to experience the capabilities of the Trader Plan, ensuring it aligns with their high-volume data needs before making a subscription commitment. Here's an overview of what the Trader Trial Plan entails.
## Pricing
**$0 for 30 days** — no credit card required. The trial is strictly limited to 30 days and **cannot be renewed or extended**.
Because no credit card is required to start the trial, **you will never be auto-billed when it ends**. If you take no action, your account automatically converts to the [Free Forever Plan](https://www.marketdata.app/docs/account/plans/free-forever) at the end of the 30 days. To keep premium features, you can subscribe to the [Trader Plan](https://www.marketdata.app/docs/account/plans/trader) for $30/month (billed annually) or $75/month at any time.
## What You Get with the Trader Trial
- **100,000 API Credits per Day**: Test the limits with a substantial increase in daily API requests, catering to the most demanding projects.
- **24-Hour Delayed Data for Stocks and Options**: Access to data that is delayed by at least 24 hours for both stocks and options.
- **1 Year Of Historical Data Access**: Explore our extensive historical data archives and fetch data from the last year.
- **Full Historical Data Access (AAPL Ticker)**: Fetch complete historical data from our extensive archives for the AAPL ticker with no date restrictions.
- **Premium Endpoints (AAPL Ticker)**: Dive into our premium endpoints using the AAPL ticker for a taste of our detailed fundamental data, including earnings insights.
## Features of the Trader Trial
The Trader Trial Plan offers a glimpse into the full capabilities of the Trader Plan with some tailored differences:
- **Duration**: Enjoy a full 30 days to explore and evaluate the features of the Trader Plan, ensuring it meets your high-volume data needs.
- **No Credit Card Required**: Begin your trial with no financial commitment, allowing for a risk-free exploration of our highest-tier services.
- **Full Access with Limitations**: While the trial provides access to many features akin to the paid Trader Plan, it's designed for evaluation purposes only and not as a complete substitute for the paid plan.
## What You Don't Get With the Trader Trial
- **No Real-Time Stock Data**: Access to real-time stock data is not included in the trial.
- **No Real-Time Options Data**: Access to real-time options data is not included in the trial.
- **No 15-Minute Delayed Options Data**: Access to 15-minute delayed options data is not available under this trial.
- **Historical Data Access (All Tickers)**: Historical data access on tickers other than AAPL is limited to 1 year.
- **Premium Endpoints (All Tickers)**: Full access to our premium endpoints across all tickers is reserved for paying customers only.
## Comparison with Other Plans
This comparison helps you understand the Trader Trial in relation to our other offerings:
| Feature | Free Forever | Trader Trial | Trader Plan |
|--------------------|--------------|-----------------|-----------------|
| API Credits | 100 per day | 100,000 per day | 100,000 per day |
| Stock Data Delay | 24 hours | 24 hours | Real-time\* |
| Options Data Delay | 24 hours | 24 hours | Real-time |
| Historical Data | 1 Year | 1 Year | Full Access |
| Standard Endpoints | ✅ | ✅ | ✅ |
| Premium Endpoints | ❌ | AAPL Only | ✅ |
\* For endpoint-level freshness rules and the Delayed → Historical rollover timing (especially for options, where data only rolls at 9:30 AM ET the next trading day), see [Data Freshness](../data-freshness.md).
## Why Try the Trader Trial?
The Trader Trial offers an unparalleled opportunity to experience the full depth and breadth of our financial data services tailored for high-volume users and professional traders. It's a risk-free way to determine if the Trader Plan's extensive data access and capabilities align with your project's needs.
Start your journey with Market Data's Trader Trial today and explore the ultimate in financial data analysis and access without any upfront commitment.
---
# Trader Plan
Source: https://www.marketdata.app/docs/account/plans/trader/index.md
The **Trader Plan** is specifically designed for high-volume users and traders who demand the utmost from their financial data services. With an enormous allocation of API credits and access to the most comprehensive data sets available, the Trader Plan is the ultimate choice for those who need maximum performance and depth in their financial analysis. Here's what you can expect from the Trader Plan.
## Pricing
- **Annual**: $30/month (billed annually at $360/year)
- **Monthly**: $75/month
## What You Get with the Trader Plan
- **100,000 API Credits per Day**: Cater to high-demand projects with an extensive number of daily API requests.
- **Real-Time Data for Stocks\* and Options**: Immediate access to financial data for both stocks and options, ensuring you're always working with the most current information. Note that real-time data is only available to users who qualify as non-professionals. [Learn more about professional status requirements](https://www.marketdata.app/education/stocks/professional-status-explained/).
- **Full Historical Data Access**: Unrestricted access to our entire historical data archive, perfect for deep historical analysis and backtesting without limitations.
- **Premium Endpoints**: Full access to all our premium endpoints, including detailed fundamental data, to provide a comprehensive view of market dynamics.
## Advantages of the Trader Plan
The Trader Plan is packed with features to support the most demanding projects:
- **Unparalleled Data Access**: With the highest number of daily API credits, real-time data for both stocks and options, and full historical access, you're equipped to handle any data challenge.
- **Comprehensive Market Insights**: Premium endpoints offer insights into earnings, company fundamentals, and more, allowing for detailed market analysis.
- **Scalability**: Designed to support projects as they grow in scope and complexity, ensuring your data needs are always met.
## Visual Comparison
Here's how the Trader Plan compares to our other offerings:
| Feature | Free Forever | Starter Plan | Trader Plan |
|--------------------|--------------|----------------|-----------------|
| API Credits | 100 per day | 10,000 per day | 100,000 per day |
| Stock Data Delay | 24 hours | Real-time\* | Real-time\* |
| Options Data Delay | 24 hours | 15 minutes | Real-time |
| Standard Endpoints | ✅ | ✅ | ✅ |
| Premium Endpoints | ❌ | ✅ | ✅ |
| Historical Data | 1 Year | 5 Years | Full Access |
\* For endpoint-level freshness rules, see [Data Freshness](../data-freshness.md).
## Why Choose the Trader Plan?
The Trader Plan is ideal for professional traders, high-volume users, and projects that require the most detailed and up-to-date financial data available. It's designed to provide the tools and data necessary for sophisticated analysis and decision-making.
Elevate your financial data capabilities with the Trader Plan and gain the edge you need to succeed in today's fast-paced markets.
---
# Review Us
Source: https://www.marketdata.app/docs/account/review-us/index.md
We welcome your reviews and feedback on social media and specialized software review sites. If you would like to share your experience with Market Data with the internet, please consider tagging us on social media or leaving us a review at any of the sites listed below. Thank you for spreading the word.
## Social Media
- Twitter - [@MarketDataApp](https://twitter.com/MarketDataApp)
- YouTube - [@MarketData-App](https://www.youtube.com/channel/UCtMeqaVWugbYyCYIAKlyC6Q)
- Facebook - [MarketDataApp](https://www.facebook.com/MarketDataApp/)
## Consumer Review Sites
- [Google Workspace Marketplace](https://workspace.google.com/marketplace/app/market_data/453586334945)
- [TrustPilot](https://www.trustpilot.com/review/www.marketdata.app)
## B2B Review Sites
- [ProductHunt](https://www.producthunt.com/products/market-data)
- [GetApp](https://www.getapp.com/it-management-software/a/market-data/)
- [Capterra](https://www.capterra.com/p/10004187/Market-Data/)
- [G2](https://www.g2.com/products/market-data/reviews)
- [SourceForge](https://sourceforge.net/software/product/Market-Data/)
- [Slashdot](https://slashdot.org/software/p/Market-Data/)
---
# Troubleshooting (Account)
Source: https://www.marketdata.app/docs/account/troubleshooting/index.md
This section covers common account issues and how to resolve them. Most problems are related to account verification — specifically, providing incomplete or insufficient information during sign-up.
## Account Verification
After you subscribe to a paid plan, Market Data must verify your identity and employment information before activating real-time data on your account. This is a requirement of the stock and options exchanges. If your profile is incomplete or doesn't match third-party records, verification will be delayed.
- [Incomplete Profile](https://www.marketdata.app/docs/docs/account/troubleshooting/incomplete-profile) — Missing or vague employer, job title, or contact information.
- [LinkedIn Profile Issues](https://www.marketdata.app/docs/docs/account/troubleshooting/linkedin-issues) — Fixing missing, private, outdated, or mismatched LinkedIn profiles (or verifying without one).
- [Retired or Not Working](https://www.marketdata.app/docs/docs/account/troubleshooting/retired-or-unemployed) — Work history requirements for subscribers who are not currently employed.
- [Self-Employed or Business Owner](https://www.marketdata.app/docs/docs/account/troubleshooting/self-employed) — Additional documentation needed when you own or operate a business.
- [Financial Industry Employer](https://www.marketdata.app/docs/docs/account/troubleshooting/financial-employer) — Why subscribers at banks, brokerages, and financial institutions receive additional review.
- [Disagree With Your Classification](https://www.marketdata.app/docs/docs/account/troubleshooting/classification-dispute) — What to do if you believe your professional/non-professional classification is incorrect.
## Billing
- [Billing Portal Access](https://www.marketdata.app/docs/docs/account/troubleshooting/billing-portal-access) — Problems accessing the billing portal or managing your subscription.
## Account Management
- [Account Deletion](https://www.marketdata.app/docs/docs/account/troubleshooting/account-deletion) — How to request deletion of your account and personal data.
---
# Account Deletion
Source: https://www.marketdata.app/docs/account/troubleshooting/account-deletion/index.md
## Overview
You can request the deletion of your Market Data account and all associated personal data at any time. Account deletion is permanent and cannot be undone.
## How to Request Deletion
Contact our [support helpdesk](https://www.marketdata.app/dashboard/) and request that your account and personal data be deleted. Please include the email address associated with your account.
## What Happens Next
1. Our support team will acknowledge your request and confirm the details.
2. You will receive a confirmation email notifying you that your account and data have been deleted.
3. Your account, profile information, and personal data will be permanently removed from our systems. If you have an active subscription, access ends immediately.
Once deletion is complete, you will no longer be able to log in to the Customer Dashboard or access any account features. This is permanent — we cannot recover a deleted account.
## What Is Deleted
- Your account login and dashboard access
- Your API tokens and usage history
- Any personal data not subject to a regulatory retention requirement (see below)
## What Is Retained
The stock and options exchanges require Market Data to maintain records of all subscriber classifications (professional vs. non-professional) for a minimum of **3 years**. To comply with this obligation, we are required to retain the following even after account deletion:
- Your name and employment information
- Your professional/non-professional classification and the basis for that determination
- Your signed exchange agreements
This data is retained solely for regulatory compliance purposes. It is not used for marketing or any other purpose.
Additionally, billing records held by our payment processor are subject to their own data retention policies and are outside of our control.
## Before You Request Deletion
- **Active subscriptions lose access immediately.** If you request deletion while you have an active paid subscription, your account will be deleted right away — you will not retain access for the remainder of your billing period. No prorated refunds will be issued. If you want to use your subscription through the end of the current billing period, request a [cancellation](https://www.marketdata.app/docs/docs/account/cancellations/) instead. Cancellation stops future charges but keeps your account active until the period ends.
- **Download your invoices.** Once your account is deleted, you will not be able to access the [Billing Portal](https://cc.payproglobal.com/Customer/Account/Login) to download past invoices. Download any invoices you need before requesting deletion.
## Still Need Help?
If you have questions about what data we hold or how deletion works, contact our [support helpdesk](https://www.marketdata.app/dashboard/) before submitting your request.
We typically respond within a few hours during market hours or within 1 business day outside of market hours.
---
# Billing Portal Access
Source: https://www.marketdata.app/docs/account/troubleshooting/billing-portal-access/index.md
## Problem Overview
You are having trouble logging in to the [Billing Portal](https://cc.payproglobal.com/Customer/Account/Login) to manage your subscription, update your payment method, or view your invoices.
## Common Issues
### "I Don't Have a Billing Portal Account"
The Billing Portal is only available to **paid customers**. A billing portal account is created automatically when you make your first purchase. If you have never had a paid subscription with Market Data, you do not have a billing portal account.
- **Free Forever accounts** do not have billing portal access.
- **Trial accounts** do not have billing portal access.
If you are on a free or trial plan and want to subscribe, you can do so directly from the Market Data [Customer Dashboard](https://www.marketdata.app/dashboard/). A billing portal account will be created for you when you complete your first purchase.
### "I Don't Know My Password"
The Billing Portal is operated by a third-party payment processor (PayProGlobal) and is **completely separate** from your Market Data account. Your Market Data login credentials will not work in the Billing Portal.
There is no initial password — you must create one the first time you log in:
1. Visit the [Billing Portal](https://cc.payproglobal.com/Customer/Account/Login).
2. Click **"Forgot Password"**.
3. Enter the email address you used on the credit card payment form when you first subscribed.
4. Follow the instructions in the password reset email to set your password.
5. Log in with your new credentials.
### "I'm Not Receiving the Password Reset Email"
The password reset email is sent to the email address that was used on the **original credit card payment form** — not necessarily the email address currently on your Market Data account.
If you have changed your email address with Market Data since your first purchase, the billing portal still uses the **original email from the payment form**. Try the password reset using that earlier email address.
If you are unsure which email address was used, contact our [support helpdesk](https://www.marketdata.app/dashboard/) and we can help you identify the correct email.
## Still Need Help?
If you are still unable to access the Billing Portal, contact our [support helpdesk](https://www.marketdata.app/dashboard/). You can also request billing support directly through the [Billing Portal](https://cc.payproglobal.com/Customer/Account/Login) once logged in — see [Billing Portal](https://www.marketdata.app/docs/docs/account/billing-portal/) for the full list of actions available.
We typically respond within a few hours during market hours or within 1 business day outside of market hours.
---
# Disagree With Your Classification
Source: https://www.marketdata.app/docs/account/troubleshooting/classification-dispute/index.md
## Problem Overview
You believe you should be classified as a non-professional data user, but Market Data has classified your account as professional. This page explains how classifications are determined, why your account may have been flagged, and what you can do if you believe the classification is incorrect.
## How Professional Status Is Determined
Professional status is not based on how you think of yourself — it is defined by the stock and options exchanges based on three objective criteria:
1. **How you use the data** — Any use of market data for business or commercial purposes is professional use, even if the business is not in financial services.
2. **Your job function** — Roles that involve trading, portfolio management, financial analysis, or investment advisory are classified as professional.
3. **Registration** — Being registered or qualified with a securities regulatory body (SEC, FINRA, CFTC, FCA, etc.) typically qualifies as professional, even if you are not currently active.
Market Data does not set these rules. They come directly from the exchanges and apply to all vendors who distribute real-time market data. See [Professional Status Policy](https://www.marketdata.app/docs/docs/account/data-policies/professional-status/) for the full criteria, or read [Professional Status Explained](https://www.marketdata.app/education/stocks/professional-status-explained/) for a detailed guide with examples.
## Common Reasons for Professional Classification
### Using Data for a Business or Startup
If you plan to use market data in connection with a business — including a startup, an LLC, or a side project — that is commercial use under exchange rules, regardless of whether:
- The business is profitable yet
- You are the sole owner or employee
- The business is not in financial services
- You personally paid for the subscription
Any use of data to support a business function — such as building a product, providing a service, generating reports for clients, or integrating data into an application — is classified as professional. This includes personal LLCs, even single-member pass-through entities.
Non-professional plans are for **personal, non-commercial use only**. If you need data for a business, the [Commercial Plan](https://www.marketdata.app/docs/docs/account/plans/commercial/) is made for business use. [Contact our sales team](https://www.marketdata.app/contact/) to get started.
### Job Function Suggests Professional Use
If your employer and job title suggest that your role involves securities-related activities, we may classify your account as professional even if you signed up under a personal account. This applies when your role involves:
- Trading or executing securities, commodities, or derivatives
- Managing investment portfolios or making investment decisions
- Providing investment advice or financial planning to clients
- Producing investment research
- Developing software or systems that support investment activities
For example, a financial analyst at an investment firm who signs up for a personal account would still be classified as professional, because their job function involves market data. The exchanges require this classification regardless of whether the individual intends to use the data at work or at home.
## What You Can Do
### If You Believe the Classification Is Wrong
If you believe your role or intended use does not meet the criteria for professional status, contact our [support helpdesk](https://www.marketdata.app/dashboard/) and provide:
- A clear description of your **day-to-day job responsibilities** (if the classification is based on your employer)
- A description of **how you intend to use the data** (if the classification is based on commercial use)
- Any supporting documentation — such as a LinkedIn profile, resume, or job description — that clarifies your role
We will review the information and update your classification if the evidence supports it. Our goal is to classify every subscriber correctly — not to default to professional.
### If the Classification Is Correct
If your use case or job function does qualify as professional under exchange rules, your options are:
- **License real-time exchange data directly.** Professional subscribers never receive real-time exchange data on a self-service plan, no matter which agreements they sign. Real-time access for professional use requires direct exchange licensing, which Market Data arranges individually. [Contact our sales team](https://www.marketdata.app/contact/) to discuss it.
- **Continue with delayed or historical data.** If you are not eligible for non-professional real-time data, you can still use Market Data to access delayed or historical data based on each exchange's policies.
- **Cancel your subscription** if the pricing does not work for you. See [Cancellations](https://www.marketdata.app/docs/docs/account/cancellations/).
## Still Need Help?
If you are unsure why your account was classified as professional, or if you want to discuss your specific situation, contact our [support helpdesk](https://www.marketdata.app/dashboard/). We are happy to walk you through the classification criteria and help you determine the correct status.
We typically respond within a few hours during market hours or within 1 business day outside of market hours.
---
# Financial Industry Employer
Source: https://www.marketdata.app/docs/account/troubleshooting/financial-employer/index.md
## Problem Overview
Your account verification requires additional review because your employer is a bank, brokerage, insurance company, investment firm, or other financial institution. Working at a financial institution does not automatically make you a professional data user — but it does require us to look more closely at your actual job responsibilities before we can classify your account.
## Why Financial Industry Employees Get Additional Review
Exchange regulations require Market Data to classify every subscriber as either a professional or non-professional data user. For most subscribers, a job title and employer are enough to make this determination. But at financial institutions, the same company can employ both professional and non-professional users depending on the role:
| Role | Classification | Reason |
|-------------------------------|------------------|-------------------------------------------------------------------------------|
| Portfolio Manager | Professional | Makes investment decisions |
| Investment Adviser | Professional | Provides tailored investment advice to clients |
| Trader | Professional | Executes securities or derivatives trades |
| Research Analyst | Professional | Produces investment research that influences trading decisions |
| Software Developer (fin-tech) | Professional | Develops software that supports investment activities |
| Administrative Assistant | Non-Professional | Provides administrative support without engaging in investment activities |
| Customer Service Rep | Non-Professional | Handles inquiries without providing investment advice |
| Compliance Officer | Non-Professional | Ensures regulatory compliance but does not engage in investment decisions |
| HR Manager | Non-Professional | Manages human resources, not involved in investment activities |
| Accountant | Non-Professional | Handles financial records and reporting, not directly involved in investments |
Because we cannot determine your classification from your employer name alone, we need to understand what you actually do day-to-day.
## How to Fix
Contact our [support helpdesk](https://www.marketdata.app/dashboard/) and provide a brief description of your **actual daily job responsibilities**. Specifically, we need to know whether your role involves any of the following:
- Trading, executing, or managing securities, commodities, or derivatives
- Providing investment advice or recommendations to clients
- Managing investment portfolios or making investment decisions
- Producing research that influences trading or advisory decisions
- Developing software or systems that directly support investment activities
If your role does **not** involve any of the above — for example, if you work in IT, operations, HR, compliance, accounting, marketing, or administrative support — you will typically be classified as non-professional, even though your employer is a financial institution.
### Supporting Documentation
To speed up the review, you can also provide any of the following:
- A link to your **LinkedIn profile** showing your current role and responsibilities
- A **resume or CV** that describes your job function
- A **company directory listing** or job description that outlines your responsibilities
## What Determines Your Classification
Your classification is based on your **job function**, not your employer. The key question is whether your day-to-day work involves securities-related activities — not whether the company you work for is in financial services.
For example, a software developer at a social media company is non-professional, but a software developer at a fin-tech startup building trading tools is professional. The same logic applies within a bank: an HR manager is non-professional, but a relationship manager advising clients on investments is professional.
See [Professional Status Policy](https://www.marketdata.app/docs/docs/account/data-policies/professional-status/) for the full classification criteria, or read [Professional Status Explained](https://www.marketdata.app/education/stocks/professional-status-explained/) for a detailed guide with additional examples.
## If You Are Classified as Professional
Professional subscribers never receive real-time exchange data on a self-service plan, no matter which exchange agreements they sign. Real-time access for professional use requires direct exchange licensing, which Market Data arranges individually. [Contact our sales team](https://www.marketdata.app/contact/) to discuss it. Delayed / historical data access remains available on your current plan, depending on the data policies of each exchange.
## Still Need Help?
If you are unsure how your role would be classified, contact our [support helpdesk](https://www.marketdata.app/dashboard/) and describe your day-to-day responsibilities. Our support team will let you know what classification applies.
We typically respond within a few hours during market hours or within 1 business day outside of market hours.
---
# Incomplete Profile
Source: https://www.marketdata.app/docs/account/troubleshooting/incomplete-profile/index.md
## Problem Overview
Your account verification is delayed because your profile contains missing, vague, or placeholder information. Market Data must verify your identity and employment details before activating real-time data, and we cannot complete verification when required fields are incomplete or do not match third-party records.
## Common Examples
The following are examples of profile entries that will prevent verification from completing:
### Employer Field
- **"N/A"** or left blank — An employer is required for all subscribers. If you are retired or not working, see [Retired or Not Working](https://www.marketdata.app/docs/docs/account/troubleshooting/retired-or-unemployed). If you are self-employed, see [Self-Employed or Business Owner](https://www.marketdata.app/docs/docs/account/troubleshooting/self-employed).
- **"Government"** — This is too vague. We need the specific agency or department (e.g., "U.S. Department of the Treasury" or "Ministry of Finance, UAE").
- **A job title instead of an employer** — For example, entering "Software Engineer" or "Investor" in the employer field. The employer field must contain the name of the company or organization you work for.
### Job Title Field
- **"N/A"**, **"Investor"**, or left blank — A specific job title is required. "Investor" is not a job title — it describes how you intend to use the data, not your employment.
- **An employer name instead of a job title** — The job title field should describe your role (e.g., "Financial Analyst", "IT Manager", "Retired").
- **Same value in both fields** — Entering your job title in both the employer and job title fields, or your employer name in both fields.
### Contact Information
- **Incomplete address** — A full mailing address is required, including city, state/province, and country.
- **Invalid phone number** — The phone number must be a working number where you can be reached.
## Why This Is Required
Market Data is required by the stock and options exchanges to verify the identity and employment of every paid subscriber before granting access to real-time data. We cross-reference the information you provide against third-party databases, including LinkedIn and FINRA BrokerCheck, to classify you as a professional or non-professional user.
When fields are missing or contain placeholder values, we cannot complete this verification and your account remains in a pending state. See [Account Verification Policy](https://www.marketdata.app/docs/docs/account/data-policies/account-verification/) for full details.
Completing your profile activates real-time data only if the review classifies you as non-professional. Professional subscribers never receive real-time exchange data on a self-service plan, no matter which exchange agreements they sign. Real-time access for professional use requires direct exchange licensing — [contact our sales team](https://www.marketdata.app/contact/) to discuss it.
## How to Fix
1. Log in to the [Customer Dashboard](https://www.marketdata.app/dashboard/).
2. Navigate to your profile settings.
3. Update all fields with accurate, specific information:
- **Employer**: The full legal name of the company or organization you work for.
- **Job Title**: Your actual role or position at that company.
- **Address**: Your complete mailing address.
- **Phone Number**: A valid phone number.
4. Save your changes.
Once your profile is updated, verification will be completed within 1 business day.
## Still Need Help?
If you have updated your profile and your account has not been activated, please contact our [support helpdesk](https://www.marketdata.app/dashboard/) and let our support team know that you have updated your profile with complete information.
We typically respond within a few hours during market hours or within 1 business day outside of market hours.
---
# LinkedIn Profile Issues
Source: https://www.marketdata.app/docs/account/troubleshooting/linkedin-issues/index.md
## Problem Overview
Your account verification is delayed because we cannot verify your employment information from the LinkedIn profile you provided. This is one of the most common reasons for verification delays — more often than not, the profile is unusable rather than missing entirely.
## Common Scenarios
- **No LinkedIn profile provided** — You entered the placeholder URL (`https://www.linkedin.com`) or left the field blank.
- **Broken URL** — The LinkedIn link you provided does not work or leads to a page that does not exist.
- **Company page instead of personal profile** — You provided a link to your company's LinkedIn page instead of your own. A company page tells us where you work, but not your specific role.
- **Private profile** — Your LinkedIn profile is set to private, so we cannot view your employment history.
- **Stub profile** — You created a LinkedIn profile to complete the sign-up form, but it does not list your employer, job title, or work history. A profile with only a name and photo is not sufficient for verification.
- **Name mismatch** — The name on your LinkedIn profile does not match the name on your data use profile. We cannot confirm that the LinkedIn profile belongs to you if the names do not match.
- **Employment mismatch** — The employment on your data use profile does not match what your LinkedIn profile shows. For example, you declared one employer or job title but your LinkedIn shows a different active role. We cannot confirm your declared employment when the two sources disagree.
## Why We Need This
Market Data is required by the stock and options exchanges to verify the identity and employment of every paid subscriber before granting access to data. A LinkedIn profile is the fastest way for us to do this because it is a widely used, publicly accessible professional database.
When we cannot verify your employment through LinkedIn, we need alternative documentation to complete the process. See [Account Verification Policy](https://www.marketdata.app/docs/docs/account/data-policies/account-verification/) for full details.
## How to Fix
You have two options:
### Option 1: Fix Your LinkedIn Profile
Make sure your LinkedIn profile is:
- **Public** (at least your employment section)
- **Accessible** via the URL you provided
- **A personal profile**, not a company page
- **Consistent** — the employment shown matches what you declared on your data use profile (if a role has ended, make sure it has an end date)
- **Matches the name** on your data use profile
- **Complete enough** to confirm your role (more than just a name and photo)
Once updated, contact our [support helpdesk](https://www.marketdata.app/dashboard/) with a link to your profile.
### Option 2: Provide Alternative Documentation
If you do not have a LinkedIn profile or prefer not to use one, you can provide any of the following:
- A current resume or CV
- A photo of your business card showing your current job title and employer
- A recent paystub (please redact any sensitive financial data)
- A company directory or website listing that shows your name and role
- Any other document that confirms your current employer and role
This documentation helps us confirm your employment so we can classify your account correctly and ensure you receive the appropriate pricing for market data access.
## Still Need Help?
If you are unsure what documentation to provide, please contact our [support helpdesk](https://www.marketdata.app/dashboard/) and our support team will let you know exactly what is needed to complete your verification.
We typically respond within a few hours during market hours or within 1 business day outside of market hours.
---
# Retired or Not Working
Source: https://www.marketdata.app/docs/account/troubleshooting/retired-or-unemployed/index.md
## Problem Overview
Your account verification is delayed because you indicated that you are retired or not currently employed, but we do not have sufficient documentation to confirm this. The stock and options exchanges require us to independently verify the employment status of every paid subscriber before activating real-time data — a self-reported status of "retired" alone is not sufficient.
## Why This Is Required
Exchange regulations require Market Data to classify every subscriber as either a professional or non-professional data user. We cannot classify you as non-professional until we receive external confirmation that you are no longer employed in a role that would qualify as professional use.
These rules come directly from the exchanges and apply before we can activate access to real-time market data. See [Account Verification Policy](https://www.marketdata.app/docs/docs/account/data-policies/account-verification/) and [Professional Status Policy](https://www.marketdata.app/docs/docs/account/data-policies/professional-status/) for full details.
## How to Fix
Provide **one** of the following documents to confirm your retirement or unemployment status:
1. A link to your **LinkedIn profile** that clearly shows your most recent role with an **end date**, or otherwise indicates that you are retired.
2. A **resume or CV** that includes the **end date of your last position** and indicates you are no longer working.
3. An official **retirement letter** or documentation from your previous employer.
4. A **pension award letter**, **Social Security benefit statement**, or other government retirement document.
5. **Tax documentation** showing retirement income (e.g., Form 1099-R in the U.S.). You may redact your Social Security Number and any other sensitive financial details — we only need to confirm that the document reflects retirement income.
6. Any other reasonable documentation that confirms you are no longer actively employed in any professional capacity.
Contact our [support helpdesk](https://www.marketdata.app/dashboard/) with any of the documents listed above. All documentation will remain confidential and used solely to verify your status.
### What We Look For
The key detail in any document is a **clear end date** for your most recent position. A LinkedIn profile or resume that only lists a job title and employer without an end date does not confirm that you have left the role — it looks the same as a current position.
## Special Circumstances
### Consulting or Part-Time Work
If you are doing consulting work, even on a part-time basis, this may affect your classification. Include details about the nature of the consulting you perform when contacting support so we can determine the correct classification.
### Retired From One Field, Working in Another
If you have retired from one career but are now working in a different field, please provide details of your current role. As long as the work is not securities-related, we can typically classify your account as non-professional.
### Selected "Retired" for Privacy Reasons
If you are still employed and selected "retired" because you preferred not to share your employment details, you can simply provide your current job title and employer instead. As long as your work is not securities-related, we can typically process your account as non-professional. See [Account Verification Policy](https://www.marketdata.app/docs/docs/account/data-policies/account-verification/) for details on what information we collect and how we use it.
## If You Are Classified as Professional
Professional subscribers never receive real-time exchange data on a self-service plan, no matter which exchange agreements they sign. Real-time access for professional use requires direct exchange licensing, which Market Data arranges individually. [Contact our sales team](https://www.marketdata.app/contact/) to discuss it. Delayed / historical data access remains available on your current plan, depending on the data policies of each exchange.
## Still Need Help?
If you are unsure what documentation to provide, please contact our [support helpdesk](https://www.marketdata.app/dashboard/) and our support team will let you know exactly what is needed to complete your verification.
We typically respond within a few hours during market hours or within 1 business day outside of market hours.
---
# Self-Employed or Business Owner
Source: https://www.marketdata.app/docs/account/troubleshooting/self-employed/index.md
## Problem Overview
Your account verification is delayed because you indicated that you are self-employed or own a business, but we cannot determine what your business does. When there is no public information available about your business — no website, no LinkedIn page, no public filings describing the business activity — we need additional documentation before we can classify your account.
## Why This Is Required
Exchange regulations require Market Data to classify every subscriber as either a professional or non-professional data user. For self-employed subscribers, the classification depends on the nature of the business itself — specifically, whether the business is involved in financial services, investment management, or other activities that would qualify as professional use under exchange rules.
A self-reported business name alone is not sufficient. We need to independently confirm what the business does. See [Account Verification Policy](https://www.marketdata.app/docs/docs/account/data-policies/account-verification/) and [Professional Status Policy](https://www.marketdata.app/docs/docs/account/data-policies/professional-status/) for full details.
## How to Fix
### Option 1: Provide a Public Link
The fastest way to resolve this is to provide a link to any public source that describes your business:
- A business website
- A company LinkedIn page
- A business directory listing
- A public filing that describes the nature of the business
Contact our [support helpdesk](https://www.marketdata.app/dashboard/) with the link and we can typically verify your account the same day.
### Option 2: Answer a Few Questions
If your business does not have a public presence, contact our [support helpdesk](https://www.marketdata.app/dashboard/) with the following information:
1. The **registered name** of your business (if applicable).
2. Your **primary business activities and services** — what does the business do?
3. Whether your business:
- Provides any investment advice or recommendations
- Manages investments for others
- Uses market data as part of its products or services
- Shares or redistributes market data to others
If your business is not involved in any of the above, we can typically classify your account as non-professional.
### Option 3: Provide Supporting Documentation
You can also send any document that confirms your line of business:
- A business registration or filing that describes the nature of the business
- A brochure or marketing materials describing your products or services
- Any other document that makes clear what the business does
## What Triggers Professional Classification
Your business activity — not your job title — determines your classification. Businesses that do any of the following are generally classified as professional:
- Provide investment advice or financial planning
- Manage investments or trade on behalf of others
- Use market data as part of a commercial product or service
- Redistribute or display market data to third parties
If your business is in a non-financial field (e.g., construction, healthcare, retail, technology), you will typically be classified as non-professional regardless of the fact that you are self-employed.
## If You Are Classified as Professional
Professional subscribers never receive real-time exchange data on a self-service plan, no matter which exchange agreements they sign. Real-time access for professional use requires direct exchange licensing, which Market Data arranges individually. [Contact our sales team](https://www.marketdata.app/contact/) to discuss it. Delayed / historical data access remains available on your current plan, depending on the data policies of each exchange.
If your business uses market data in a product or service, the [Commercial Plan](https://www.marketdata.app/docs/docs/account/plans/commercial/) is made for it: real-time stock and options prices with a commercial license, and no exchange licensing or exchange fees.
## Still Need Help?
If you are unsure how your business would be classified, please contact our [support helpdesk](https://www.marketdata.app/dashboard/) and describe what your business does. Our support team will let you know what classification applies and what documentation, if any, is needed.
We typically respond within a few hours during market hours or within 1 business day outside of market hours.
---
# Plan Upgrades
Source: https://www.marketdata.app/docs/account/upgrades/index.md
At Market Data, we strive to make your experience with our services as seamless and positive as possible. When you decide to upgrade your plan, we ensure that your unused time is not wasted. The method we use to calculate your savings is the same, but the way you receive the credit is slightly different depending on whether you're moving from a monthly plan to an annual plan or from an annual plan to a monthly plan. Here's how we calculate and apply your unused time towards your new plan:
## Switching from a Monthly Plan to an Annual Plan
If you decide to upgrade before your current monthly subscription period ends, we calculate the credit from the unused portion of your monthly plan. This credit is then applied as a discount on the upfront cost of your new annual plan.
### Step-by-Step Process:
1. **Calculate unused days**: We start by figuring out how many days remain in your current monthly subscription.
2. **Determine the daily rate**: We calculate the daily rate of your monthly plan by dividing the total cost by 30 (average number of days per month).
3. **Calculate your credit**: We multiply the number of unused days by the daily rate to find out how much credit you have.
4. **Apply this credit**: This amount is then discounted from the price of your new annual plan.
### Examples
Let's look at two specific examples to understand how the discounts are calculated and applied when upgrading to an annual plan:
#### Example 1: From Monthly Starter Plan to Annual Starter Plan
- **Original Plan**: Monthly Starter Plan costing $30/month.
- **New Plan**: Annual Starter Plan costing $144/year ($12/month billed annually).
**Scenario**: Suppose you are halfway through your monthly cycle when you decide to upgrade.
- **Unused Time Calculation**: 15 days remaining in the month.
- **Daily Rate**: $30 / 30 = $1 per day.
- **Credit Amount**: 15 days x $1/day = $15.
- **Discount Applied**: Your new annual plan will be discounted by $15, so you pay $144 - $15 = $129 initially.
#### Example 2: From Monthly Trader Plan to Annual Trader Plan
- **Original Plan**: Monthly Trader Plan costing $75/month.
- **New Plan**: Annual Trader Plan costing $360/year ($30/month billed annually).
**Scenario**: Suppose you decide to upgrade with 10 days left in your monthly plan.
- **Unused Time Calculation**: 10 days remaining.
- **Daily Rate**: $75 / 30 = $2.50 per day.
- **Credit Amount**: 10 days x $2.50/day = $25.00.
- **Discount Applied**: Your new annual plan will be discounted by $25.00, so you pay $360 - $25.00 = $335.00 initially.
## Switching from an Annual Plan to a Monthly Plan
When you upgrade to a different plan before your current annual subscription expires, we calculate the credit you will receive based on the percentage of unused days remaining in your subscription. This credit is then applied to extend the renewal date of your new, upgraded plan.
### Step-by-Step Calculation:
1. **Determine the unused days**: First, we count how many days are left until your current plan was set to expire.
2. **Calculate the percentage of the year left**: We calculate what percentage of the year remains from the moment you upgrade.
3. **Determine the credit**: We apply this percentage to the cost of your annual plan to figure out your credit amount.
4. **Apply the credit to your new plan**: This credit is then used to extend the renewal date of your newly purchased monthly plan.
### Examples
To give you a clearer picture, here are two examples of how the credit is calculated and applied:
#### Example 1: Upgrading from Annual Trader Plan to Prime Plan
- **Original Plan**: Annual Trader Plan costing $360/year ($30/month billed annually).
- **New Plan**: Prime Plan costing $250/month ($8.33 per day)
**Scenario**: Suppose you decide to upgrade with 90 days left in your subscription year.
- **Unused Time Calculation**: 90 days / 365 days = ~24.7% of the year left.
- **Credit Amount**: 24.7% of $360 = $88.92.
- **Application of Credit**: This $88.92 will be credited to your new Prime plan, extending the renewal date by 11 days.
#### Example 2: Upgrading from Annual Starter Plan to Prime Plan
- **Original Plan**: Annual Starter Plan costing $144/year ($12/month billed annually).
- **New Plan**: Prime Plan costing $250/month. ($8.33 per day)
**Scenario**: Suppose you decide to upgrade with 200 days left in your subscription year.
- **Unused Time Calculation**: 200 days / 365 days = ~54.8% of the year left.
- **Credit Amount**: 54.8% of $144 = $78.91.
- **Application of Credit**: This $78.91 will be credited to your new Prime plan, extending the renewal date by 9 days.
## How To Get More Information
We strive to make upgrading your plan a seamless and value-retaining process. At Market Data, we guarantee that every day of your subscription is accounted for, ensuring that you can upgrade your plan whenever you wish without forfeiting the financial worth of your initial investment. Should you have any questions or uncertainties about upgrading, please don't hesitate to reach out to us.
---
# Market Data API Documentation
Source: https://www.marketdata.app/docs/api/index.md
The Market Data API is designed around REST and supports standard HTTP response codes and methods. All responses are delivered via JSON for programmatic use of the information or via CSV files to load into your preferred spreadsheet application.
> [!NOTE]
> **[Root Endpoint]**
>
> https://api.marketdata.app/
> [!CAUTION]
> **[Accept HTTP 203 as success]**
>
> Any endpoint may return HTTP `203 Non-Authoritative Information` instead of `200 OK` when the response is served from our caching tier. This is normal — the body is identical in shape, and you should treat 203 exactly the same as 200. Many HTTP clients (and many OpenAPI-generated SDKs) default to treating only 200 as success; you must update your client to also accept 203 or your integration will silently fail in production.
>
> A `204 No Content` response can also occur when `mode=cached` is requested and no cached data is available — see [Data Mode](https://www.marketdata.app/docs/api/universal-parameters/mode) and [Troubleshooting](https://www.marketdata.app/docs/api/troubleshooting) for the full list of status codes.
## Try Our API
The easiest way to try out our API is using our [Swagger User Interface](https://api.marketdata.app/), which will allow you to try out your API requests directly from your browser.
> [!TIP]
> Our endpoints have **lots of optional parameters** to allow users to sort and filter responses. It can be overwhelming to new users at first. When you're first getting started testing our API in Swagger, scroll to the required parameters and ignore all the optional ones. Most endpoints require only a ticker symbol as a required parameter.
### Get Started Quick — No Registration Required!
You can try stock, option, and mutual fund endpoints with several different symbols that are unlocked and require no authorization token. That means these symbols can be used throughout our API with no registration required!
- Stock endpoints: Use **AAPL**.
- Options endpoints: Use any AAPL contract, for example: **AAPL271217C00250000**.
- Mutual fund endpoints: Use **VFINX**.
Once you would like to experiment with other symbols, [register a free account](https://www.marketdata.app/signup/) (no credit card required) and you be able to choose a free trial. After your trial ends, if you decide not to subscribe, you will still get 100 free requests per day. Make the decision to pay only after making a complete trial of our API.
---
# Authentication
Source: https://www.marketdata.app/docs/api/authentication/index.md
The Market Data API uses a **Bearer Token** for authentication. The token is a programmatic representation of your username and password credentials, so you must keep it secret just as you would your username and password. The token is required for each request you make to the API.
> [!CAUTION]
> **[Accept HTTP 203 as success]**
>
> Any endpoint may return HTTP `203 Non-Authoritative Information` instead of `200 OK` when the response is served from our caching tier. The body is identical in shape — treat 203 exactly the same as 200. Code samples below that use raw HTTP libraries (rather than our SDKs) must accept both status codes; checking only `status == 200` will silently drop valid responses. See [Troubleshooting](https://www.marketdata.app/docs/api/troubleshooting) for the full list of status codes.
## Obtaining a Token
To obtain it, sign-in to your customer dashboard using your username and password and request a token. It will be delivered by email to the address you used to sign-in.
## Using the Token
There are two ways to pass this token to the API with your requests:
1. Header Authentication
2. URL Parameter Authentication
> [!TIP]
> We recommend using header-based authentication to ensure your token is not stored or cached. While Market Data makes a conscientious effort to delete tokens from our own server logs, we cannot guarantee that your token will not be stored by any of our third party cloud infrastructure partners.
## Header Authentication
Add the token to the ```Authorization``` header using the word ```Bearer```.
### Code Examples
### HTTP
```http
GET /v1/stocks/quotes/SPY/ HTTP/1.1
Host: api.marketdata.app
Accept: application/json
Authorization: Bearer {token}
```
> [!TIP]
> The curly braces around token are a placeholder for this example. Do not actually wrap your token with curly braces.
### JavaScript
```js title="app.js"
import { MarketDataClient } from "@marketdata/sdk";
// The SDK reads your token from the MARKETDATA_TOKEN environment variable.
// Alternatively, pass it directly: new MarketDataClient({ token: "your_token_here" })
const client = new MarketDataClient();
try {
const quotes = await client.stocks.quotes("SPY");
console.log(quotes);
} catch (error) {
console.error(error);
}
```
For more information about using the JavaScript SDK, see our [JavaScript SDK documentation](https://www.marketdata.app/docs/sdk/js) and [authentication guide](https://www.marketdata.app/docs/sdk/js/authentication).
### TypeScript
```typescript title="app.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { StockQuote } from "@marketdata/sdk";
// The SDK reads your token from the MARKETDATA_TOKEN environment variable.
// Alternatively, pass it directly: new MarketDataClient({ token: "your_token_here" })
const client = new MarketDataClient();
try {
const quotes: StockQuote[] = await client.stocks.quotes("SPY");
console.log(quotes);
} catch (error) {
console.error(error);
}
```
For more information about using the JavaScript SDK, see our [JavaScript SDK documentation](https://www.marketdata.app/docs/sdk/js) and [authentication guide](https://www.marketdata.app/docs/sdk/js/authentication).
### Python
```python
from marketdata import MarketDataClient
# Initialize the client (token is read from MARKETDATA_TOKEN environment variable)
# Or pass it directly: client = MarketDataClient(token="your_token_here")
client = MarketDataClient()
# Get stock quotes for SPY
quotes = client.stocks.quotes("SPY")
print(quotes)
```
For more information about using the Python SDK, see our [Python SDK documentation](https://www.marketdata.app/docs/sdk/py) and [authentication guide](https://www.marketdata.app/docs/sdk/py/authentication).
### Go
```go
// Import the Market Data SDK
import api "github.com/MarketDataApp/sdk-go"
func main() {
// Create a new Market Data client instance
marketDataClient := api.New()
// Set the token for authentication
// Replace "your_token_here" with your actual token
marketDataClient.Token("your_token_here")
// Now the client is ready to make authenticated requests to the Market Data API
// Use the client to create a StockQuoteRequest
sqr, err := api.StockQuote(marketDataClient).Symbol("SPY").Get()
if err != nil {
fmt.Println("Error fetching stock quotes:", err)
return
}
// Process the retrieved quote
for _, quote := range quotes {
fmt.Printf(quote)
}
}
```
### Java
```java title="App.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.stocks.StockQuoteRequest;
public class App {
public static void main(String[] args) {
// The no-arg constructor reads your token from the MARKETDATA_TOKEN
// environment variable (or a .env file in the working directory).
// Alternatively, pass it directly:
// new MarketDataClient("your_token_here", null, null, true)
try (MarketDataClient client = new MarketDataClient()) {
client.stocks().quote(StockQuoteRequest.of("SPY")).values().forEach(System.out::println);
}
}
}
```
### Kotlin
```kotlin title="App.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.stocks.StockQuoteRequest
fun main() {
// The no-arg constructor reads your token from the MARKETDATA_TOKEN
// environment variable (or a .env file in the working directory).
// Alternatively, pass it directly:
// MarketDataClient("your_token_here", null, null, true)
MarketDataClient().use { client ->
client.stocks().quote(StockQuoteRequest.of("SPY")).values().forEach(::println)
}
}
```
### C#
```csharp title="Program.cs"
using MarketDataApp;
// With no options supplied, the SDK reads your token from the MARKETDATA_TOKEN
// environment variable (or a .env file / user secrets) and validates it on startup.
// Alternatively, pass it directly:
// await MarketDataClient.CreateAsync(new MarketDataClientOptions { ApiToken = "your_token_here" })
using var client = await MarketDataClient.CreateAsync();
foreach (var quote in (await client.Stocks.GetQuoteAsync("SPY")).Values)
{
Console.WriteLine(quote);
}
```
## URL Parameter Authentication
Add the token as a variable directly in the URL using the format ```token=YOUR_TOKEN_HERE```. For example:
```
https://api.marketdata.app/v1/stocks/quotes/SPY/?token={token}
```
> [!TIP]
> The curly braces around token are a placeholder for this example. Do not actually wrap your token with curly braces.
## Demo The API With No Authentication
You can try stock and option endpoints with several different symbols that are unlocked and do not require a token. Please be aware that only historical data for these tickers is available without a token.
- Try any stock endpoint with **AAPL**, no token required.
- Try any option endpoint with any AAPL contract, for example: **AAPL271217C00250000**. No token required.
## IP Address Restrictions
Each account may only connect from one IP address at a time. You can switch devices, but you cannot use two devices simultaneously. Back-and-forth switching between IP addresses within a 5-minute window triggers a temporary block.
See the [Single IP Address Policy](https://www.marketdata.app/docs/docs/account/data-policies/single-ip/) for full details, or [403: Multiple IP Addresses](https://www.marketdata.app/docs/docs/api/troubleshooting/multiple-ip-addresses/) if your account is blocked.
---
# CORS
Source: https://www.marketdata.app/docs/api/cors/index.md
The Market Data API includes **Cross-Origin Resource Sharing (CORS)** headers as a convenience for subscribers who want to make API requests directly from a browser during development or for personal use. CORS support is not an invitation to build public-facing applications — self-service plans are personal licenses and [data redistribution](https://www.marketdata.app/docs/docs/account/data-policies/data-redistribution) is not permitted.
## Permitted Use Cases
CORS is intended for **personal, non-public use** of the API in a browser environment. Examples of permitted use:
- **Local development and prototyping** — building and testing apps on `localhost`.
- **Personal dashboards and tools** — browser-based apps that only you access.
These use cases are consistent with your self-service plan's personal license. The data you retrieve is for your own consumption — you are not making it available to others.
## What Is Not Permitted
Using CORS to serve market data to other people is **redistribution**, regardless of whether your app is free or paid. This includes:
- Public websites or apps that display market data to visitors
- Browser-based products or services that expose Market Data's data to your users or customers
- Shared dashboards or tools accessible to people outside your organization
These are the uses the [Commercial Plan](https://www.marketdata.app/docs/docs/account/plans/commercial/) is made for: it includes the right to display data to your users, with no exchange licensing and no per-user fees. See the [Data Redistribution Policy](https://www.marketdata.app/docs/docs/account/data-policies/data-redistribution) for details, or [contact our sales team](https://www.marketdata.app/contact/) to get started.
## CORS Headers
The API returns the following CORS headers on all responses, including preflight (`OPTIONS`) requests:
| Header | Value |
|--------------------------------|----------------------------------------------------------------------------------------------------------------|
| `Access-Control-Allow-Origin` | `*` |
| `Access-Control-Allow-Methods` | `DELETE, GET, OPTIONS, PATCH, POST, PUT` |
| `Access-Control-Allow-Headers` | `accept, accept-encoding, authorization, content-type, dnt, origin, user-agent, x-csrftoken, x-requested-with` |
| `Access-Control-Max-Age` | `86400` (24 hours) |
## Token Security
When you use the API from a browser, your token is included in client-side code that **anyone with access to the page can inspect**. Only use your token in browser environments where you control who has access.
> [!CAUTION]
> **[Never Expose Your Token on a Public Website]**
>
> Do not embed your API token in any website, page, or app that is accessible to the public internet without authentication. Anyone who visits the page can extract your token from the network requests or source code and use it to make requests on your account, consuming your credits and accessing your data.
>
> If your token is compromised, contact our helpdesk via the [customer dashboard](https://www.marketdata.app/dashboard/) immediately to have it revoked and reissued.
## Example
The following example is suitable for local development or a personal/internal app:
```javascript
const response = await fetch("https://api.marketdata.app/v1/stocks/quotes/AAPL/", {
headers: {
Authorization: "Bearer YOUR_TOKEN",
},
});
const data = await response.json();
console.log(data);
```
---
# Dates and Times
Source: https://www.marketdata.app/docs/api/dates-and-times/index.md
## Response Timestamps
By default, all timestamps returned by the API are **Unix timestamps** (seconds since January 1, 1970 UTC). You can change the output format using the [`dateformat`](https://www.marketdata.app/docs/docs/api/universal-parameters/date-format) universal parameter, which supports `unix` (default), `timestamp` (ISO 8601 with timezone), and `spreadsheet` formats. Refer to individual endpoint documentation for details on how timestamps are set for each response type.
## Response Timezone
> [!NOTE]
> **[All Timestamps Use Eastern Time]**
>
> All US market timestamps returned by the Market Data API are anchored to the **America/New_York** timezone (US Eastern Time). This applies to **all asset classes**, including equities and options. Even though many US-listed options trade on Chicago exchanges, the API normalizes all US timestamps to Eastern Time. The API automatically adjusts for **daylight saving time** (EST/EDT).
During Eastern Standard Time (EST, UTC-5), midnight Eastern is 05:00 UTC. During Eastern Daylight Time (EDT, UTC-4), midnight Eastern is 04:00 UTC. You do not need to handle this offset yourself — simply parse all timestamps using the `America/New_York` timezone identifier and your date library will resolve the correct date.
### Date-Based Timestamps
For responses that represent a **calendar date** rather than a specific moment in time — such as daily stock candles — the Unix timestamp is set to **midnight Eastern Time (00:00 America/New_York)** on the relevant date.
For example, a daily candle for **March 6, 2026** has Unix timestamp `1772773200`, which equals **05:00 UTC** (= 00:00 Eastern during EST). The 05:00 UTC value does not represent a market event — it is simply midnight Eastern expressed as a UTC-based Unix timestamp. The date portion (March 6) identifies the trading session.
> [!CAUTION]
> **[Common Mistake]**
>
> If you parse a daily timestamp as UTC instead of Eastern Time, the date may appear shifted. For example, Unix timestamp `1772773200` is `2026-03-06 05:00:00 UTC` but `2026-03-06 00:00:00 America/New_York`. If your code extracts the date using UTC, it will produce the correct result in this case, but during EDT the offset changes to 04:00 UTC and edge cases can cause the date to shift by a day. Always use `America/New_York` when extracting dates from these timestamps.
> [!TIP]
> **[SDK Users]**
>
> If you use an official [Market Data SDK](https://www.marketdata.app/docs/docs/sdk), timezone handling is done for you automatically. The SDKs convert all API timestamps to proper date and time objects in America/New_York — for example, Carbon in PHP, datetime in Python, and time.Time in Go — so you don't need to manage timezone conversion yourself.
### Real-Time and Intraday Timestamps
For real-time quotes, intraday candles, and `updated` fields, the timestamp represents the actual moment the data was captured or last refreshed. These are standard Unix timestamps and can be converted to any timezone, but they correspond to Eastern Time market hours (regular session 9:30 AM – 4:00 PM ET).
### Options Timestamps
Although US-listed options primarily trade on exchanges in Chicago (CBOE), the API returns all option timestamps in **Eastern Time**, consistent with the rest of the API. The `updated` field on option quotes and chain responses reflects the Eastern Time snapshot of the data.
Option expiration timestamps reflect the **actual expiration time**, not midnight. Equity options expire at **4:00 PM Eastern** and index options expire at **4:15 PM Eastern**.
## Request Date Formats
All Market Data endpoints support advanced date-handling features to allow you to work with dates in a way that works best for your application. Our API will accept date inputs in any of the following formats:
- **American Numeric Notation** Dates and times in MM/DD/YYYY format. For example, closing bell on Dec 30, 2020 for the NYSE would be: 12/30/2020 4:00 PM.
- **Timestamp** An ISO 8601 timestamp in the format YYYY-MM-DD. For example, closing bell on Dec 30, 2020 for the NYSE would be: 2020-12-30 16:00:00.
- **Unix** Dates and times in unix format (seconds after the unix epoch). For example, closing bell on Dec 30, 2020 for the NYSE would be: 1609362000.
- **Spreadsheet** Dates and times in spreadsheet format (days after the Excel epoch). For example, closing bell on Dec 30, 2020 for the NYSE would be: 44195.66667
- **Relative Dates and Times** Keywords or key phrases that indicate specific days, relative to the current date. For example, "today" or "yesterday".
- **Option Expiration Dates** Keyphrase that select specific dates that correspond with dates in the US option expiration calendar.
## Relative Dates and Times
This feature allows you to use natural language to specify dates and times in a way that is easy for humans to read and understand, but can be tricky for machines to parse.
Relative dates allow Market Data endpoints to continually modify the date sent to the endpoint based on the current date. We have a lot of relative date keywords supported already and quite a few more planned for the future, so keep an eye out on this section for continual improvements to this feature.
- **Time-based Parameters** Time keyphrases let you select a specific time of day, relative to the current time. Time-based parameters are typically used to model intraday stock movements.
- `now` Equivalent to the current time. Use this keyword to select the current open candle, for example.
- `-[number] minutes` Use negative minutes to specify a time in the past _n_ minutes before. When this is used alone, it is relative to the current time. When used in conjunction in `from` field (i.e. the starting date/time), it is relative to the `to` field (i.e. the ending date/time). For example, if the current time is 10:30 AM, but 10:00 AM is used in the `to` field and `-10 minutes` in the `from` field, then the starting time will be 9:50 AM. The query would return values from 9:50 AM to 10:00 AM. However, if the `to` field were to be omitted, then the same query would return data from 10:20 AM to 10:30 AM since `-10 minutes` would be relative to the current time of 10:30 AM.
- `[number] minutes ago` The `minutes ago` keyword lets you select a relative time, _n_ minutes before the current time. For example, if the time is 10:00 AM then `30 minutes ago` would refer to 9:30 AM of the current day.
- `-[number] hours` Use negative hours to specify a time in the past _n_ hours before. When this is used alone, it is relative to the current time. When used in conjunction in `from` field (i.e. the starting date/time), it is relative to the `to` field (i.e. the ending date/time). For example, if the current time is 10:30 AM, but 10:00 AM is used in the `to` field and `-1 hour` in the `from` field, then the starting time will be 9:00 AM. The query would return values from 9:00 AM to 10:00 AM. However, if the `to` field were to be omitted, then the same query would return data from 9:30 AM to 10:30 AM since `-1 hour` would be relative to the current time of 10:30 AM.
- `[number] hours ago` The `hours ago` keyword lets you select a relative time, _n_ hours before the current time. For example, if the time is 4:00 PM then `4 hours ago` would refer to 12:00 PM of the current day.
- **Daily Parameters** Daily keyphrases let you select a specific day, relative to the current day.
- `today` Equivalent to today's date.
- `yesterday` Yesterday's date.
- `last session` Returns the date of the last closed market session. This keyphrase always gets the previous closed session, accounting for weekends, holidays, and half-days (which are counted as valid sessions). For example, on Monday during market hours, it would return Friday's date. After market close on Monday, it would return Monday's date. This allows you to always track the previous session without having to account for weekends, holidays, or other market closures.
- `-[number] days` Use negative days to specify a time in the past _n_ days before. When this is used alone, it is relative to the current day. When used in conjunction in `from` field (i.e. the starting date), it is relative to the `to` field (i.e. the ending date). For example, if the current date is January 20, but January 10 is used in the `to` field and `-5 days` in the `from` field, then the starting day will be January 5. The query would return values from January 5 to January 10. However, if the `to` field were to be omitted, then the same query would return data from January 15 to January 20 since `-5 days` would be relative to the current date of January 20.
- `[number] days ago` The `days ago` keyword lets you select a relative day, _n_ days before the current date. For example, if today is January 5, 2024, then using `2 days ago` would select the date January 3, 2024.
- **Weekly Parameters** Weekly keyphrases let you select a day of the week in the current, previous, or following week.
- `-[number] weeks` Use negative weeks to specify a date in the past _n_ weeks before. When this is used alone, it is relative to the current day. When used in conjunction in `from` field (i.e. the starting date), it is relative to the date in the `to` field (i.e. the ending date). For example, if the current date is October 15, 2023 but October 8 is used in the `to` field and `-1 week` in the `from` field, then the starting day will be October 2, 2023. The query would return values from October 2 to October 8. However, if the `to` field were to be omitted, then the same query would return data from October 9 to October 15 since `-5 days` would be relative to the current date of January 20.
- `[number] weeks ago` The `weeks ago` keyword lets you select a relative week, _n_ weeks before the current date. For example, if today is January 1, 2024, then using `2 weeks ago` would select the date January 3, 2024.
- **Monthly Dates** Monthly keyphrases let you select a specific day of a specific month.
- `-[number] months` Use negative months to specify a date in the past _n_ months before. When this is used alone, it is relative to the current day. When used in conjunction in `from` field (i.e. the starting date), it is relative to the date in the `to` field (i.e. the ending date). For example, if the current date is October 15 but October 8 is used in the `to` field and `-1 month` in the `from` field, then the starting day will be September 8. The query would return values from September 8 to October 8. However, if the `to` field were to be omitted, then the same query would return data from September 15 to October since `-1 month` would be relative to the current date of October 15.
- `[number] months ago` The months ago keyword lets you select a relative date, _n_ months before the current date. For example, if today is January 5, 2024, then using `3 months ago` would select the date October 5, 2023.
- **Yearly Dates** Yearly keyphrases let you select a specific day of in the current, previous, or following year.
- `-[number] years` Use negative years to specify a date in the past _n_ years before. When this is used alone, it is relative to the current day. When used in conjunction in `from` field (i.e. the starting date), it is relative to the date in the `to` field (i.e. the ending date). For example, if the current date is October 15, 2023 but October 8, 2023 is used in the `to` field and `-1 year` in the `from` field, then the starting day will be September 8, 2022. The query would return values from September 8, 2022 to October 8, 2023. However, if the `to` field were to be omitted, then the same query would return data from September 15, 2022 to October 15, 2023 since `-1 year` would be relative to the current date of October 15, 2023.
- `[number] years ago` The years ago keyword lets you select a relative date, 365 days before the current date. For example, if today is January 5, 2024, then using `2 years ago` would select the date January 5, 2022.
> [!CAUTION]
> **[Coming Soon]**
>
>
> The following relative date parameters are planned for the future and have not yet been implemented.
- **Time-based Parameters** Time keyphrases let you select a specific time of day, relative to the current time. Time-based parameters are typically used to model intraday stock movements.
- `at open`, `opening bell`, `market open` These keyphrases let you select the opening time for the market day. The phase is relative to each exchange's opening time. For example, if you were trading AAPL in the United States, using `at open` would set a time of 9:30 AM ET.
- `at close`, `closing bell`, `market close` These keyphrases let you select the closing time for the market day. The phase is relative to each exchange's closing time. For example, if you were trading AAPL in the United States, using `at close` would set a time of 4:00 PM ET.
- `[number] [minutes|hours] before [open|close]` These before keyword lets you select a relative time before market open or close. For example `30 minutes before close` would select the time 3:30 PM ET if you are trading a stock on a U.S. exchange.
- `[number] [minutes|hours] after [open|close]` These after keyword lets you select a relative time after market open or close. For example `1 hour after open` would select the time 10:30 AM ET if you are trading a stock on a U.S. exchange.
- **Weekly Parameters** Weekly keyphrases let you select a day of the week in the current, previous, or following week.
- `this [day of the week]` Works the same way as specifying the day without adding _this_. The day in the _current_ week. For example, if today is Tuesday and the expression is `this Monday`, the date returned would be yesterday. If the expression were `this Wednesday` the date returned would be tomorrow. The word _this_ is optional. If it is omitted, the keyword will still return the date in the current week that corresponds with the day mentioned.
- `last [day of the week]` The day in the _previous_ week. For example, if today is Tuesday and the expression used is `last Monday`, it would not refer to the Monday that occurred yesterday, but the Monday 8 days prior that occurred in the previous week.
- `next [day of the week]` The day in the _following_ week. For example, if today is Monday and the expression is `next Tuesday` it would not refer to tomorrow, but the Tuesday that occurs 8 days from now.
- **Monthly Dates** Monthly keyphrases let you select a specific day of a specific month.
- `[ordinal number] of [the|this] month` - The nth day of the current month. For example, if today is September 10th and the phrase used is, `8th of this month` the date returned would be September 8. The keyphrase `of [the/this] month` is optional. Using a single ordinal number `8th` will also return the 8th of the current month.
- `[ordinal number] of last month` - The nth day of the current month. For example, if today is December 15th and the phrase used is, `8th of last month` the date returned would be November 8.
- `ordinal number] of next month` - The nth day of the following month. For example, if today is December 15th and the phrase used is, `8th of next month` the date returned would be January 8 of the following year.
- `last day of [the|this|last|next] month` - Using the `last day of` keyword will always select the final day of the month. Since months can end on the 28th, 29th, 30th, or 31st, this keyword allows you to always select the final day of a month. For example: `last day of this month`, `last day of next month`. It can also be used to select the last day in February without needing to determine whether the current year is a leap year, `last day of february`.
- `ordinal number] [day of the week] of [the|this|last|next] month` - Combine ordinal numbers and weekdays to specify a specific day of the week in the current, previous, or following month. For example, ``3nd Friday of last month``.
- `last [day of the week] of [the|this|last|next] month` - Selects the last day of the week in a month relative to the current month. If the last Monday of the month is needed, instead of using the keyphrase `4th Monday of this month`, it is safer to use `last Monday of this month`, since months can have 4 or 5 Mondays, depending on length.
- `last [day of the week] in [month` - Selects the last day of the week in a specific month. For example, Memorial Day could be selected by using the keyphrase `last Monday in May`.
- **Yearly Dates** Yearly keyphrases let you select a specific day of in the current, previous, or following year.
- `[month] [number]` A specific date in the current year. For example `February 18` would return February 18 of the current year.
- `[month] [number] [this|last|next] year` A specific date in the current, previous, or following year. For example, if today was Dec 31, 2022, `February 18 next year` would return February 18, 2023.
## Option Expiration Dates
Option expiration dates let you target the expiration dates for option contracts. Dates are based on the US option expirations calendar and are only meant for use with US markets.
> [!CAUTION]
>
> Option date parameters are planned for the future and have not yet been implemented.
>
> ---
>
> Option-related keyphrases cannot be used to return expiration dates far in the future for options which have not yet been traded or for options in the past which have already expired. For example, if today is January 15, 2023, you couldn't use `November 2023's 1st weekly expiration` since weekly options for November would not exist yet. The API will return a `No data` response if you try to request an expiration that does not exist, whether in the future or the past.
- **Monthly Expirations** - Target a relative month or specific month's option expiration date.
- `[month] [year] expiration` - The standard monthly option expiration date for [month] during [year]. This is useful for targeting the expiration date for a specific month. Although options normally expire the 3rd Friday, sometimes market holidays can modify this schedule. Using an option expiration keyphrase will ensure that you always obtain the exact date that options expire in a specific month. For example, if today was January 1, 2022, using `December expiration` or `December 2022 expiration` would both return _December 16, 2022_.
- [year] is optional. If [month] is used without [year] the lookup is relative to the current date and expired options will not be returned. For example, if today is April 8, 2022, `January expiration` will return January 20, 2023 and not the options which expired in January of 2022.
- `this|last|next] month's expiration` - Returns the monthly option expiration date for the current, previous, or following month relative to the current month. For example if today is October 5, 2022, and `next month's expiration` is used, the date returned would be _November 18, 2022_.
> [!TIP]
>
> Not all underlying tickers offer weekly or quarterly options. Before building an API request that uses them, ensure that your underlying offers weekly or quarterly option contracts.
- **Weekly Expirations** - Target a relative week or specific week's option expiration date.
- `[this|last|next] week's expiration` - Returns the weekly option expiration date for the current, previous, or following week relative to the current week. For example if today is October 5, 2022, and `next week's expiration` is used, the date returned would be _October 14, 2022_.
- `expiration in [number] weeks` - Returns closest expiration that will occur [number] weeks from today without taking into account the current week. For example, if today is August 1, 2022 the phrase `expiration in 6 weeks` would return September 16, 2022.
- `[month] [year] [ordinal number] weekly expiration` - Returns the nth option expiration date for [month] during [year]. When both a month and year are combined, this can be used to lookup a weekly option date for an expired or unexpired option. For example, `March 2020's 2nd expiration` would return _March 14, 2020_.
- **Quarterly Expirations** - Returns a quarterly expiration date for a relative date or specifically targeted date.
- `[ordinal number] quarter's expiration` - Returns the quarterly option expiration date for the 1st, 2nd, 3rd, or 4th quarter in the current financial year. For example if today is March 1, 2022, and `4th quarter's expiration` is used, the date returned would be _December 30, 2022_. This will lookup both expired and unexpired options.
- `[this|last|next] quarter's expiration` - Returns the quarterly option expiration date for the current, previous, or following quarter relative to the current date. For example if today is March 1, 2026, and `this quarter's expiration` is used, the date returned would be _March 31, 2026_.
- `[expiration in [number] quarters` - Returns closest quarterly expiration that will occur [number] quarters from today without taking into account the current quarter. For example, if today is March 1, 2022 the phrase `expiration in 2 quarters` would return September 30, 2022.
- `[year] [ordinal number] quarter expiration` - Returns the option expiration date for [nth] quarter during [year]. For example, `2020's 2nd quarter expiration` would return _June 30, 2020_.
- **Specific Contract Expirations** - Target a specific date based on when a contract is first traded or when it expires.
- `at expiration` - Returns the expiration date for the option contract. This must be used in the context of a specific option contract. For example, if you used at expiration with AAPL271217C00250000, the date returned would be December 17, 2027.
- `first traded` - Returns the date when the contract was traded for the first time. This must be used in the context of a specific option contract. For example, if you used first traded with AAPL271217C00250000, the date returned would be March 13, 2025.
---
# Mutual Funds
Source: https://www.marketdata.app/docs/api/funds/index.md
The mutual funds endpoints offer access to historical pricing data for mutual funds.
## Root Endpoint For Mutual Funds
```
https://api.marketdata.app/v1/funds/
```
## Funds Endpoints
---
# Mutual Fund Candles
Source: https://www.marketdata.app/docs/api/funds/candles/index.md
Get historical price candles for a mutual fund.
> [!WARNING]
> This endpoint will be live on May 1, 2024. Before May 1, use the stocks/candles endpoint to query mutual fund candles.
## Endpoint
```
https://api.marketdata.app/v1/funds/candles/{resolution}/{symbol}/
```
### Method
```
GET
```
## Request Example
### HTTP
**GET** [https://api.marketdata.app/v1/funds/candles/D/VFINX/?from=2024-01-01&to=2024-01-31](https://api.marketdata.app/v1/funds/candles/D/VFINX/?from=2024-01-01&to=2024-01-31)
### JavaScript
```js title="fundCandles.js"
import { MarketDataClient } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
// VFINX is available without authentication (free test symbol).
const candles = await client.funds.candles("VFINX", {
resolution: "D",
from: "2024-01-01",
to: "2024-01-31",
});
for (const c of candles) {
console.log(`t=${c.t} o=${c.o} h=${c.h} l=${c.l} c=${c.c}`);
}
} catch (error) {
console.error(error);
}
```
### TypeScript
```typescript title="fundCandles.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { FundsCandle } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
// VFINX is available without authentication (free test symbol).
const candles: FundsCandle[] = await client.funds.candles("VFINX", {
resolution: "D",
from: "2024-01-01",
to: "2024-01-31",
});
for (const c of candles) {
console.log(`t=${c.t} o=${c.o} h=${c.h} l=${c.l} c=${c.c}`);
}
} catch (error) {
console.error(error);
}
```
### Python
```python title="fundCandles.py"
from marketdata import MarketDataClient
client = MarketDataClient()
candles = client.funds.candles("VFINX", resolution="D", from_date="2024-01-01", to_date="2024-01-31")
print(candles)
```
### Go
```go title="fundCandles.go"
import (
"fmt"
api "github.com/MarketDataApp/sdk-go"
)
func ExampleFundCandlesRequest() {
fcr, err := FundCandles().Resolution("D").Symbol("VFINX").From("2024-01-01").To("2024-01-31").Get()
if err != nil {
fmt.Print(err)
return
}
for _, candle := range fcr {
fmt.Println(candle)
}
}
```
### PHP
```php title="fundCandles.php"
use MarketDataApp\Client;
$client = new Client();
$candles = $client->mutual_funds->candles(
symbol: "VFINX",
from: "2024-01-01",
to: "2024-01-31",
resolution: "D"
);
// Display formatted candles summary
echo $candles;
```
### Java
```java title="FundCandles.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.funds.FundCandlesRequest;
import com.marketdata.sdk.funds.FundResolution;
import java.time.LocalDate;
public class FundCandles {
public static void main(String[] args) {
try (MarketDataClient client = new MarketDataClient()) {
// VFINX is available without authentication (free test symbol).
client.funds()
.candles(FundCandlesRequest.builder(FundResolution.DAILY, "VFINX")
.from(LocalDate.parse("2024-01-01"))
.to(LocalDate.parse("2024-01-31"))
.build())
.values()
.forEach(System.out::println);
}
}
}
```
### Kotlin
```kotlin title="FundCandles.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.funds.FundCandlesRequest
import com.marketdata.sdk.funds.FundResolution
import java.time.LocalDate
fun main() {
MarketDataClient().use { client ->
// VFINX is available without authentication (free test symbol).
client.funds()
.candles(
FundCandlesRequest.builder(FundResolution.DAILY, "VFINX")
.from(LocalDate.parse("2024-01-01"))
.to(LocalDate.parse("2024-01-31"))
.build()
)
.values()
.forEach(::println)
}
}
```
### C#
```csharp title="FundCandles.cs"
using MarketDataApp;
using MarketDataApp.Funds;
using var client = await MarketDataClient.CreateAsync();
// VFINX is available without authentication (free test symbol).
var candles = await client.Funds.GetCandlesAsync(
FundResolution.Daily, "VFINX",
from: DateOnly.Parse("2024-01-01"), to: DateOnly.Parse("2024-01-31"));
foreach (var candle in candles.Values)
{
Console.WriteLine(candle);
}
```
## Response Example
```json
{
"s":"ok",
"t":[1577941200,1578027600,1578286800,1578373200,1578459600,1578546000,1578632400],
"o":[300.69,298.6,299.65,298.84,300.32,302.39,301.53],
"h":[300.69,298.6,299.65,298.84,300.32,302.39,301.53],
"l":[300.69,298.6,299.65,298.84,300.32,302.39,301.53],
"c":[300.69,298.6,299.65,298.84,300.32,302.39,301.53]
}
```
## Request Parameters
### Required
- **resolution** `string`
The duration of each candle. Intraday resolutions are not supported for funds. *Case-insensitive*.
- Daily Resolutions: (`daily`, `D`, `1D`, `2D`, ...)
- Weekly Resolutions: (`weekly`, `W`, `1W`, `2W`, ...)
- Monthly Resolutions: (`monthly`, `M`, `1M`, `2M`, ...)
- Yearly Resolutions: (`yearly`, `Y`, `1Y`, `2Y`, ...)
- **symbol** `string`
The mutual fund's ticker symbol.
- **from** `date`
The leftmost candle on a chart (inclusive). If you use `countback`, `to` is not required. Accepted timestamp inputs: ISO 8601, unix, spreadsheet.
- **to** `date`
The rightmost candle on a chart (inclusive). Accepted timestamp inputs: ISO 8601, unix, spreadsheet.
- **countback** `number`
Will fetch a number of candles before (to the left of) `to`. If you use `from`, `countback` is not required.
## Response Attributes
### Success
- **s** `string`
ll always be `ok` when there is data for the candles requested.
- **o** `array[number]`
Open price.
- **h** `array[number]`
High price.
- **l** `array[number]`
Low price.
- **c** `array[number]`
Close price.
- **t** `array[number]`
Candle time. This is midnight US Eastern Time on the session date. All timestamps use US Eastern Time (America/New_York). See [Response Timezone](https://www.marketdata.app/docs/docs/api/dates-and-times#response-timezone) for details.
### No Data
- **s** `string`
Status will be `no_data` if no candles are found for the request.
- **nextTime** `number` optional
Unix time of the next quote if there is no data in the requested period, but there is data in a subsequent period.
### Error
- **s** `string`
Status will be `error` if the request produces an error response.
- **errmsg** `string`
An error message.
---
# Markets
Source: https://www.marketdata.app/docs/api/markets/index.md
The Markets endpoints provide reference and status data about the markets covered by Market Data.
## Root Endpoint For Markets
```
https://api.marketdata.app/v1/markets/
```
## Markets Endpoints
---
# Market Status
Source: https://www.marketdata.app/docs/api/markets/status/index.md
Get the past, present, or future status for a stock market. The endpoint will respond with "open" for trading days or "closed" for weekends or market holidays.
## Endpoint
```
https://api.marketdata.app/v1/markets/status/
```
### Method
```
GET
```
## Request Example
### HTTP
**GET** [https://api.marketdata.app/v1/markets/status/?from=2024-01-01&to=2024-01-31](https://api.marketdata.app/v1/markets/status/?from=2024-01-01&to=2024-01-31)
**GET** [https://api.marketdata.app/v1/markets/status/?date=yesterday](https://api.marketdata.app/v1/markets/status/?date=yesterday)
### JavaScript
```js title="app.js"
import { MarketDataClient } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const days = await client.markets.status({ from: "2024-01-01", to: "2024-01-31" });
for (const d of days) {
console.log(`date=${d.date} status=${d.status}`);
}
const yesterday = await client.markets.status({ date: "yesterday" });
console.log(yesterday[0]);
} catch (error) {
console.error(error);
}
```
### TypeScript
```typescript title="app.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { MarketStatusResponse } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const days: MarketStatusResponse = await client.markets.status({ from: "2024-01-01", to: "2024-01-31" });
for (const d of days) {
console.log(`date=${d.date} status=${d.status}`);
}
const yesterday: MarketStatusResponse = await client.markets.status({ date: "yesterday" });
console.log(yesterday[0]);
} catch (error) {
console.error(error);
}
```
### Python
```python title="app.py"
from marketdata import MarketDataClient
client = MarketDataClient()
status1 = client.markets.status(from_date="2024-01-01", to_date="2024-01-31")
status2 = client.markets.status(date="yesterday")
print(status1)
print(status2)
```
### Go
```go title="marketstatus.go"
import (
"fmt"
api "github.com/MarketDataApp/sdk-go"
)
func ExampleMarketStatus() {
msr, err := api.MarketStatus().From("2024-01-01").To("2024-01-31").Get()
if err != nil {
fmt.Print(err)
return
}
for _, report := range msr {
fmt.Println(report)
}
}
func ExampleMarketStatus_relativeDates() {
msr, err := api.MarketStatus().Date("yesterday").Get()
if err != nil {
fmt.Print(err)
return
}
for _, report := range msr {
fmt.Println(report)
}
}
```
### PHP
```php title="marketStatus.php"
use MarketDataApp\Client;
$client = new Client();
// Get market status for a date range
$statuses = $client->markets->status(
from: "2024-01-01",
to: "2024-01-31"
);
// Display formatted market statuses
echo $statuses;
// Get market status for yesterday
$yesterday = $client->markets->status(
date: "yesterday"
);
echo $yesterday;
```
### Java
```java title="MarketStatusApp.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.markets.MarketStatusRequest;
import java.time.LocalDate;
public class MarketStatusApp {
public static void main(String[] args) {
try (MarketDataClient client = new MarketDataClient()) {
client.markets()
.status(MarketStatusRequest.builder()
.from(LocalDate.of(2024, 1, 1))
.to(LocalDate.of(2024, 1, 31))
.build())
.values()
.forEach(System.out::println);
client.markets()
.status(MarketStatusRequest.builder()
.date(LocalDate.now().minusDays(1))
.build())
.values()
.forEach(System.out::println);
}
}
}
```
### Kotlin
```kotlin title="MarketStatusApp.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.markets.MarketStatusRequest
import java.time.LocalDate
fun main() {
MarketDataClient().use { client ->
client.markets()
.status(
MarketStatusRequest.builder()
.from(LocalDate.of(2024, 1, 1))
.to(LocalDate.of(2024, 1, 31))
.build()
)
.values()
.forEach(::println)
client.markets()
.status(
MarketStatusRequest.builder()
.date(LocalDate.now().minusDays(1))
.build()
)
.values()
.forEach(::println)
}
}
```
### C#
```csharp title="MarketStatusApp.cs"
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
var january = await client.Markets.GetStatusAsync(
from: new DateOnly(2024, 1, 1),
to: new DateOnly(2024, 1, 31));
foreach (var day in january.Values)
{
Console.WriteLine(day);
}
var yesterday = await client.Markets.GetStatusAsync(
date: DateOnly.FromDateTime(DateTime.Today.AddDays(-1)));
foreach (var day in yesterday.Values)
{
Console.WriteLine(day);
}
```
## Response Example
```json
{
"s": "ok",
"date": [1680580800],
"status": ["open"]
}
```
## Request Parameters
### Required
- There are no required parameters for `status`. If no parameter is given, the request will return the market status in the United States for the current day.
### Optional
- **country** `string`
Use to specify the country. Use the two digit ISO 3166 country code. If no country is specified, `US` will be assumed. Only countries that Market Data supports for stock price data are available (currently only the United States).
- **date** `date`
Consult whether the market was open or closed on the specified date. Accepted timestamp inputs: ISO 8601, unix, spreadsheet, relative date strings.
- **from** `date`
The earliest date (inclusive). If you use countback, from is not required. Accepted timestamp inputs: ISO 8601, unix, spreadsheet, relative date strings.
- **to** `date`
The last date (inclusive). Accepted timestamp inputs: ISO 8601, unix, spreadsheet, relative date strings.
- **countback** `number`
Countback will fetch a number of dates before `to` If you use from, countback is not required.
## Response Attributes
### Success
- **s** `string`
ll always be `ok` when there is data for the dates requested.
- **date** `array[dates]`
The date.
- **status** `array[string]`
The market status. This will always be `open` or `closed` or `null`. Half days or partial trading days are reported as `open`. Requests for days further in the past or further in the future than our data will be returned as `null`.
### No Data
- **s** `string`
Status will be `no_data` if no data is found for the request.
### Error
- **s** `string`
Status will be `error` if the request produces an error response.
- **errmsg** `string`
An error message.
---
# Options
Source: https://www.marketdata.app/docs/api/options/index.md
The Market Data API provides a comprehensive suite of options endpoints, designed to cater to various needs around options data. These endpoints are designed to be flexible and robust, supporting both real-time and historical data queries. They accommodate a wide range of optional parameters for detailed data retrieval, making the Market Data API a versatile tool for options traders and financial analysts.
## Root Endpoint For Options
```
https://api.marketdata.app/v1/options/
```
## Options Endpoints
---
# Option Chain
Source: https://www.marketdata.app/docs/api/options/chain/index.md
Get a current or historical end of day option chain for an underlying ticker symbol. Optional parameters allow for extensive filtering of the chain.
## Endpoint
```http
GET https://api.marketdata.app/v1/options/chain/{underlyingSymbol}/
```
## Request Example
### HTTP
**GET** [https://api.marketdata.app/v1/options/chain/AAPL/?expiration=2027-12-17&side=call](https://api.marketdata.app/v1/options/chain/AAPL/?expiration=2027-12-17&side=call)
### JavaScript
```js title="app.js"
import { MarketDataClient } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const contracts = await client.options.chain("AAPL", {
expiration: "2027-12-17",
side: "call",
});
console.log(`Got ${contracts.length} contracts`);
} catch (error) {
console.error(error);
}
```
### TypeScript
```typescript title="app.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { OptionsChain } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const contracts: OptionsChain[] = await client.options.chain("AAPL", {
expiration: "2027-12-17",
side: "call",
});
console.log(`Got ${contracts.length} contracts`);
} catch (error) {
console.error(error);
}
```
### Python
```python title="app.py"
from marketdata import MarketDataClient
client = MarketDataClient()
chain = client.options.chain("AAPL", expiration="2027-12-17", side="call")
print(chain)
```
### Go
```go title="optionChain.go"
import (
"fmt"
api "github.com/MarketDataApp/sdk-go"
)
func ExampleOptionChainRequest() {
AAPL, err := api.OptionChain().UnderlyingSymbol("AAPL").Expiration("2027-12-17").Side("call").Get()
if err != nil {
fmt.Println("Error fetching option chain:", err)
return
}
for _, contract := range AAPL {
fmt.Println(contract)
}
}
```
### PHP
```php title="optionChain.php"
use MarketDataApp\Client;
use MarketDataApp\Enums\Side;
$client = new Client();
$chain = $client->options->option_chain(
symbol: "AAPL",
expiration: "2027-12-17",
side: Side::CALL
);
// Display chain summary (expirations, contract counts)
echo $chain;
// Convenience methods available
$calls = $chain->getCalls();
$puts = $chain->getPuts();
$strikes = $chain->getStrikes();
```
### Java
```java title="OptionChain.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.options.ExpirationFilter;
import com.marketdata.sdk.options.OptionSide;
import com.marketdata.sdk.options.OptionsChainRequest;
import java.time.LocalDate;
try (MarketDataClient client = new MarketDataClient()) {
client.options().chain(
OptionsChainRequest.builder("AAPL")
.expirationFilter(ExpirationFilter.onDate(LocalDate.parse("2027-12-17")))
.side(OptionSide.CALL)
.build())
.values().forEach(System.out::println);
}
```
### Kotlin
```kotlin title="OptionChain.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.options.ExpirationFilter
import com.marketdata.sdk.options.OptionSide
import com.marketdata.sdk.options.OptionsChainRequest
import java.time.LocalDate
MarketDataClient().use { client ->
client.options().chain(
OptionsChainRequest.builder("AAPL")
.expirationFilter(ExpirationFilter.onDate(LocalDate.parse("2027-12-17")))
.side(OptionSide.CALL)
.build())
.values().forEach(::println)
}
```
### C#
```csharp title="OptionChain.cs"
using MarketDataApp;
using MarketDataApp.Options;
using var client = await MarketDataClient.CreateAsync();
var chain = await client.Options.GetChainAsync(
"AAPL",
expiration: ExpirationFilter.ForDate(DateOnly.Parse("2027-12-17")),
side: OptionSide.Call);
foreach (var contract in chain.Values)
{
Console.WriteLine(contract);
}
```
## Response Example
This is a current chain, so the Greek columns are populated. A historical request (`date=`) returns `null` for all five — see [the note on the `date` parameter](#request-parameters).
```json
{
"s": "ok",
"optionSymbol": ["AAPL271217C00005000", "AAPL271217C00010000", "AAPL271217C00015000", "AAPL271217C00020000", "AAPL271217C00025000", "AAPL271217C00030000", "AAPL271217C00035000", "AAPL271217C00040000", "AAPL271217C00045000", "AAPL271217C00050000", "AAPL271217C00055000", "AAPL271217C00060000", "AAPL271217C00065000", "AAPL271217C00070000", "AAPL271217C00075000", "AAPL271217C00080000", "AAPL271217C00085000", "AAPL271217C00090000", "AAPL271217C00095000", "AAPL271217C00100000", "AAPL271217C00105000", "AAPL271217C00110000", "AAPL271217C00115000", "AAPL271217C00120000", "AAPL271217C00125000", "AAPL271217C00130000", "AAPL271217C00135000", "AAPL271217C00140000", "AAPL271217C00145000", "AAPL271217C00150000", "AAPL271217C00155000", "AAPL271217C00160000", "AAPL271217C00165000", "AAPL271217C00170000", "AAPL271217C00175000", "AAPL271217C00180000", "AAPL271217C00185000", "AAPL271217C00190000", "AAPL271217C00195000", "AAPL271217C00200000", "AAPL271217C00205000", "AAPL271217C00210000", "AAPL271217C00215000", "AAPL271217C00220000", "AAPL271217C00225000", "AAPL271217C00230000", "AAPL271217C00235000", "AAPL271217C00240000", "AAPL271217C00245000", "AAPL271217C00250000", "AAPL271217C00255000", "AAPL271217C00260000", "AAPL271217C00265000", "AAPL271217C00270000", "AAPL271217C00275000", "AAPL271217C00280000", "AAPL271217C00285000", "AAPL271217C00290000", "AAPL271217C00295000", "AAPL271217C00300000", "AAPL271217C00305000", "AAPL271217C00310000", "AAPL271217C00315000", "AAPL271217C00320000", "AAPL271217C00325000", "AAPL271217C00330000", "AAPL271217C00335000", "AAPL271217C00340000", "AAPL271217C00345000", "AAPL271217C00350000", "AAPL271217C00355000", "AAPL271217C00360000", "AAPL271217C00365000", "AAPL271217C00370000", "AAPL271217C00375000", "AAPL271217C00380000", "AAPL271217C00385000", "AAPL271217C00390000", "AAPL271217C00395000", "AAPL271217C00400000", "AAPL271217C00405000", "AAPL271217C00410000", "AAPL271217C00415000", "AAPL271217C00420000", "AAPL271217C00425000", "AAPL271217C00430000", "AAPL271217C00435000", "AAPL271217C00440000", "AAPL271217C00450000", "AAPL271217C00460000", "AAPL271217C00470000", "AAPL271217C00480000", "AAPL271217C00490000", "AAPL271217C00500000", "AAPL271217C00510000", "AAPL271217C00520000", "AAPL271217C00530000", "AAPL271217C00540000", "AAPL271217C00550000", "AAPL271217C00560000", "AAPL271217C00570000", "AAPL271217C00580000", "AAPL271217C00590000", "AAPL271217C00600000"],
"underlying": ["AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL", "AAPL"],
"expiration": [1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200, 1829077200],
"side": ["call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call", "call"],
"strike": [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 105, 110, 115, 120, 125, 130, 135, 140, 145, 150, 155, 160, 165, 170, 175, 180, 185, 190, 195, 200, 205, 210, 215, 220, 225, 230, 235, 240, 245, 250, 255, 260, 265, 270, 275, 280, 285, 290, 295, 300, 305, 310, 315, 320, 325, 330, 335, 340, 345, 350, 355, 360, 365, 370, 375, 380, 385, 390, 395, 400, 405, 410, 415, 420, 425, 430, 435, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590, 600],
"firstTraded": [1761744600, 1761744600, 1761744600, 1761744600, 1761744600, 1761744600, 1761744600, 1761744600, 1761744600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1741872600, 1761312600, 1761312600, 1761312600, 1761312600, 1761312600, 1761312600, 1761744600, 1761744600, 1761744600, 1764858600, 1764858600, 1779111000, 1779111000, 1779111000, 1779111000, 1779111000],
"dte": [485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485],
"updated": [1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600, 1787169600],
"bid": [309.95, 305.1, 300.35, 295.6, 290.85, 286.15, 281.4, 276.7, 272, 267.25, 262.55, 257.85, 253.2, 248.5, 243.8, 239.15, 234.45, 229.8, 225.1, 220.45, 215.8, 211.15, 206.5, 201.85, 197.25, 192.65, 188.05, 183.45, 178.85, 174.3, 169.8, 165.2, 160.75, 156.25, 151.8, 147.4, 143, 138.65, 134.3, 130.2, 125.8, 121.6, 117.75, 113.4, 109.45, 105.55, 101.4, 97.55, 94.05, 90.4, 86.8, 82.95, 79.35, 76.05, 72.6, 69.95, 66.45, 63.8, 60.85, 58.2, 55.4, 52.6, 49.7, 47.3, 45.6, 42.9, 40.9, 38.9, 36.8, 35.25, 33.35, 31.4, 29.85, 28.1, 26.55, 25.05, 23.65, 22.3, 21, 20, 18.7, 17.6, 16.75, 15.65, 14.75, 13.9, 13.1, 12.4, 11, 9.7, 8.6, 7.65, 6.8, 6.1, 5.4, 4.8, 4.25, 3.8, 3.35, 3, 2.7, 2.41, 2.2, 1.99],
"bidSize": [11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 10, 10, 10, 10, 10, 10, 27, 88, 139, 101, 110, 177, 261, 207, 75, 130, 336, 304, 137, 70, 263, 189, 262, 287, 204, 352, 20, 200, 392, 121, 213, 180, 230, 152, 184, 222, 217, 146, 115, 31, 146, 42, 44, 99, 11, 56, 37, 44, 29, 44, 30, 36, 28, 41, 50, 53, 28, 34, 98, 32, 29, 86, 48, 54, 78, 23, 25, 21, 62, 28, 77, 188, 107, 35, 37, 27, 28, 71, 54],
"mid": [311.85, 307, 302.25, 297.5, 292.75, 288.05, 283.3, 278.6, 273.875, 269.15, 264.45, 259.75, 255.075, 250.4, 245.7, 241.025, 236.35, 231.675, 227, 222.35, 217.7, 213.05, 208.4, 203.75, 199.15, 194.525, 189.925, 185.35, 180.75, 176.15, 171.525, 167.075, 162.5, 158, 153.55, 149.125, 144.75, 139.95, 136.025, 131.425, 127.55, 122.9, 119.3, 114.9, 111.025, 107.2, 102.775, 99.275, 95.225, 91.7, 87.9, 84.2, 81.125, 77.325, 74.05, 70.925, 67.7, 65.075, 62.05, 59.1, 56.65, 53.6, 51.075, 48.275, 46.225, 43.75, 41.85, 39.6, 37.45, 35.65, 33.775, 31.9, 30.25, 28.575, 26.975, 25.525, 24.125, 22.725, 21.475, 20.325, 19.1, 18.05, 17.125, 16.025, 15.175, 14.3, 13.475, 12.7, 11.35, 10.05, 8.875, 7.9, 7.025, 6.35, 5.6, 5.075, 4.425, 4.025, 3.625, 3.25, 2.925, 2.63, 2.385, 2.15],
"ask": [313.75, 308.9, 304.15, 299.4, 294.65, 289.95, 285.2, 280.5, 275.75, 271.05, 266.35, 261.65, 256.95, 252.3, 247.6, 242.9, 238.25, 233.55, 228.9, 224.25, 219.6, 214.95, 210.3, 205.65, 201.05, 196.4, 191.8, 187.25, 182.65, 178, 173.25, 168.95, 164.25, 159.75, 155.3, 150.85, 146.5, 141.25, 137.75, 132.65, 129.3, 124.2, 120.85, 116.4, 112.6, 108.85, 104.15, 101, 96.4, 93, 89, 85.45, 82.9, 78.6, 75.5, 71.9, 68.95, 66.35, 63.25, 60, 57.9, 54.6, 52.45, 49.25, 46.85, 44.6, 42.8, 40.3, 38.1, 36.05, 34.2, 32.4, 30.65, 29.05, 27.4, 26, 24.6, 23.15, 21.95, 20.65, 19.5, 18.5, 17.5, 16.4, 15.6, 14.7, 13.85, 13, 11.7, 10.4, 9.15, 8.15, 7.25, 6.6, 5.8, 5.35, 4.6, 4.25, 3.9, 3.5, 3.15, 2.85, 2.57, 2.31],
"askSize": [11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 17, 17, 12, 17, 1, 15, 15, 18, 17, 17, 25, 29, 10, 42, 10, 26, 10, 86, 25, 71, 31, 12, 69, 7, 33, 10, 16, 76, 24, 20, 10, 40, 72, 60, 53, 23, 56, 59, 21, 18, 55, 43, 44, 34, 21, 21, 23, 35, 13, 10, 45, 38, 10, 38, 25, 10, 42, 55, 14, 57, 26, 113, 15, 39, 35, 15, 7, 12, 48, 41, 113, 5, 105, 29, 39, 38, 30, 99, 16],
"last": [310.06, 295, 231.29, 227.46, 223.24, 270.6, null, 214.49, 204.68, 255.03, 249.56, 242.5, 238.3, 235.95, 268.6, 229.45, 232.42, 227.75, 217.3, 210.86, 207.2, 203.4, 198.6, 193.6, 196.83, 185.05, 180.45, 176, 179, 175, 175.05, 152.1, 151.95, 150.8, 143, 138.65, 133.15, 136.4, 134.6, 132.15, 116.2, 124, 119.2, 109.17, 110.15, 106.55, 104.9, 99, 90.98, 92.1, 89.95, 83.29, 80.35, 78, 63.66, 71.55, 68.5, 66.04, 61.9, 58.85, 56.1, 53.04, 51.52, 47.75, 45.3, 42.72, 41.46, 40.67, 37.13, 34.65, 32.95, 31.5, 31, 28.3, 26.43, 25.25, 22.65, 18.2, 21.6, 20.25, 19, 16.8, 15.85, 16.5, 12.45, 13.8, 11.9, 12.33, 11.3, 9.9, 6.87, 6.5, 5.2, 6.23, 4.4, 4.66, 4.3, 3.37, 2.65, 2.83, 2.32, 2.1, 2.04, 2.16],
"openInterest": [1, 3, 25, 1, 1, 9, 0, 8, 17, 820, 411, 297, 157, 168, 132, 427, 435, 172, 151, 365, 102, 231, 384, 271, 447, 443, 108, 443, 581, 636, 94, 410, 266, 497, 342, 841, 1260, 572, 371, 3959, 875, 770, 916, 983, 2026, 1552, 584, 810, 562, 3469, 552, 2447, 618, 2537, 394, 2184, 469, 1600, 443, 8516, 778, 6420, 822, 798, 575, 2129, 2276, 2342, 715, 1644, 2073, 1061, 1453, 1122, 1108, 698, 469, 932, 494, 5461, 310, 173, 182, 479, 190, 581, 364, 7435, 2920, 536, 145, 109, 314, 790, 423, 65, 348, 157, 792, 31, 42, 31, 75, 467],
"volume": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 3, 4, 0, 2, 3, 0, 1, 3, 1, 1, 1, 4, 6, 89, 78, 89, 0, 9, 2, 6, 5, 55, 4, 30, 8, 36, 12, 8, 5, 1, 1, 150, 44, 14, 11, 3, 7, 16, 34, 0, 6, 177, 10, 4, 2, 3, 0, 1, 1, 10, 6, 7, 0, 0, 0, 2, 0, 0, 6, 0, 0, 0, 0, 0, 0, 11],
"inTheMoney": [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
"intrinsicValue": [311.9, 306.9, 301.9, 296.9, 291.9, 286.9, 281.9, 276.9, 271.9, 266.9, 261.9, 256.9, 251.9, 246.9, 241.9, 236.9, 231.9, 226.9, 221.9, 216.9, 211.9, 206.9, 201.9, 196.9, 191.9, 186.9, 181.9, 176.9, 171.9, 166.9, 161.9, 156.9, 151.9, 146.9, 141.9, 136.9, 131.9, 126.9, 121.9, 116.9, 111.9, 106.9, 101.9, 96.9, 91.9, 86.9, 81.9, 76.9, 71.9, 66.9, 61.9, 56.9, 51.9, 46.9, 41.9, 36.9, 31.9, 26.9, 21.9, 16.9, 11.9, 6.9, 1.9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"extrinsicValue": [0.05, 0.1, 0.35, 0.6, 0.85, 1.15, 1.4, 1.7, 1.975, 2.25, 2.55, 2.85, 3.175, 3.5, 3.8, 4.125, 4.45, 4.775, 5.1, 5.45, 5.8, 6.15, 6.5, 6.85, 7.25, 7.625, 8.025, 8.45, 8.85, 9.25, 9.625, 10.175, 10.6, 11.1, 11.65, 12.225, 12.85, 13.05, 14.125, 14.525, 15.65, 16.0, 17.4, 18.0, 19.125, 20.3, 20.875, 22.375, 23.325, 24.8, 26.0, 27.3, 29.225, 30.425, 32.15, 34.025, 35.8, 38.175, 40.15, 42.2, 44.75, 46.7, 49.175, 48.275, 46.225, 43.75, 41.85, 39.6, 37.45, 35.65, 33.775, 31.9, 30.25, 28.575, 26.975, 25.525, 24.125, 22.725, 21.475, 20.325, 19.1, 18.05, 17.125, 16.025, 15.175, 14.3, 13.475, 12.7, 11.35, 10.05, 8.875, 7.9, 7.025, 6.35, 5.6, 5.075, 4.425, 4.025, 3.625, 3.25, 2.925, 2.63, 2.385, 2.15],
"underlyingPrice": [316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9, 316.9],
"iv": [2.2106, 1.6815, 1.445, 1.2905, 1.1768, 1.0959, 1.0217, 0.9659, 0.9142, 0.8686, 0.8307, 0.7962, 0.767, 0.7398, 0.7124, 0.6887, 0.6664, 0.6451, 0.6249, 0.6071, 0.5899, 0.5733, 0.5572, 0.5416, 0.5286, 0.5148, 0.5022, 0.4906, 0.4783, 0.4662, 0.4534, 0.4459, 0.4347, 0.4255, 0.4173, 0.4095, 0.4025, 0.3867, 0.3881, 0.3762, 0.3768, 0.3639, 0.3672, 0.3578, 0.3554, 0.3529, 0.3424, 0.3428, 0.3361, 0.3347, 0.3297, 0.3253, 0.3263, 0.3198, 0.3176, 0.3161, 0.3131, 0.3144, 0.3117, 0.3091, 0.3097, 0.3052, 0.304, 0.3003, 0.3014, 0.2989, 0.2998, 0.2978, 0.2959, 0.2959, 0.2949, 0.2934, 0.2929, 0.2918, 0.2906, 0.2901, 0.2895, 0.2884, 0.288, 0.2878, 0.2867, 0.2865, 0.2868, 0.2854, 0.2856, 0.2852, 0.2849, 0.2846, 0.2848, 0.2842, 0.2834, 0.2835, 0.2835, 0.2848, 0.2842, 0.2857, 0.2843, 0.2859, 0.2868, 0.2873, 0.288, 0.2887, 0.2897, 0.2904],
"delta": [0.9938, 0.9928, 0.9919, 0.9913, 0.9906, 0.9899, 0.9893, 0.9885, 0.9878, 0.9871, 0.9863, 0.9854, 0.9844, 0.9833, 0.9823, 0.9812, 0.98, 0.9788, 0.9775, 0.976, 0.9744, 0.9728, 0.9711, 0.9694, 0.9672, 0.9652, 0.9628, 0.9602, 0.9577, 0.9551, 0.9526, 0.9486, 0.9455, 0.9417, 0.9373, 0.9327, 0.9275, 0.9252, 0.9165, 0.9123, 0.9031, 0.8987, 0.8874, 0.8809, 0.8711, 0.8608, 0.8535, 0.8411, 0.8312, 0.8185, 0.8069, 0.7945, 0.7797, 0.7671, 0.7524, 0.7373, 0.7221, 0.7056, 0.6897, 0.6735, 0.6567, 0.64, 0.6229, 0.6055, 0.5884, 0.5708, 0.554, 0.5364, 0.5189, 0.5021, 0.4851, 0.4679, 0.4515, 0.4349, 0.4185, 0.4028, 0.3873, 0.3717, 0.3569, 0.3428, 0.3281, 0.3146, 0.3021, 0.2881, 0.2762, 0.2641, 0.2524, 0.2411, 0.2205, 0.2005, 0.1818, 0.1653, 0.1502, 0.1377, 0.1242, 0.114, 0.1019, 0.0937, 0.0855, 0.0778, 0.0709, 0.0646, 0.0592, 0.054],
"gamma": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0002, 0.0002, 0.0002, 0.0002, 0.0002, 0.0003, 0.0003, 0.0003, 0.0003, 0.0004, 0.0004, 0.0004, 0.0005, 0.0005, 0.0006, 0.0006, 0.0006, 0.0007, 0.0008, 0.0008, 0.0009, 0.0009, 0.001, 0.0011, 0.0012, 0.0013, 0.0014, 0.0015, 0.0016, 0.0017, 0.0018, 0.0019, 0.002, 0.0021, 0.0022, 0.0024, 0.0024, 0.0026, 0.0027, 0.0028, 0.0029, 0.003, 0.0031, 0.0032, 0.0032, 0.0033, 0.0034, 0.0035, 0.0035, 0.0036, 0.0036, 0.0036, 0.0037, 0.0037, 0.0037, 0.0037, 0.0037, 0.0037, 0.0037, 0.0036, 0.0036, 0.0036, 0.0035, 0.0035, 0.0034, 0.0034, 0.0033, 0.0033, 0.0032, 0.0031, 0.0031, 0.003, 0.0028, 0.0027, 0.0026, 0.0024, 0.0022, 0.0021, 0.002, 0.0019, 0.0017, 0.0016, 0.0015, 0.0014, 0.0013, 0.0012, 0.0011, 0.001],
"theta": [-0.002, -0.0032, -0.0041, -0.0048, -0.0055, -0.0063, -0.0069, -0.0077, -0.0083, -0.009, -0.0097, -0.0105, -0.0113, -0.0121, -0.0128, -0.0136, -0.0144, -0.0152, -0.016, -0.0168, -0.0176, -0.0184, -0.0192, -0.02, -0.0209, -0.0218, -0.0227, -0.0236, -0.0245, -0.0253, -0.026, -0.0272, -0.028, -0.029, -0.0301, -0.0312, -0.0323, -0.0325, -0.0345, -0.035, -0.0369, -0.0373, -0.0395, -0.0402, -0.0416, -0.0431, -0.0435, -0.0452, -0.0459, -0.0473, -0.0482, -0.049, -0.0505, -0.051, -0.0519, -0.0528, -0.0534, -0.0545, -0.0549, -0.0553, -0.056, -0.056, -0.0562, -0.0561, -0.0564, -0.0562, -0.0563, -0.056, -0.0556, -0.0553, -0.0548, -0.0542, -0.0536, -0.0529, -0.0521, -0.0513, -0.0505, -0.0495, -0.0486, -0.0477, -0.0466, -0.0456, -0.0447, -0.0434, -0.0424, -0.0413, -0.0402, -0.0391, -0.037, -0.0348, -0.0325, -0.0305, -0.0285, -0.027, -0.025, -0.0235, -0.0216, -0.0203, -0.019, -0.0177, -0.0165, -0.0153, -0.0143, -0.0133],
"vega": [0.0204, 0.0308, 0.0388, 0.0455, 0.0515, 0.0586, 0.0641, 0.0711, 0.0773, 0.0835, 0.0907, 0.0981, 0.1067, 0.1155, 0.1234, 0.1326, 0.1421, 0.1518, 0.1617, 0.173, 0.1847, 0.1965, 0.2087, 0.2212, 0.2364, 0.2506, 0.2664, 0.2837, 0.3002, 0.317, 0.3331, 0.3574, 0.3764, 0.3991, 0.4242, 0.4505, 0.479, 0.4916, 0.5373, 0.5586, 0.6043, 0.6252, 0.6773, 0.7062, 0.7485, 0.7908, 0.8197, 0.867, 0.903, 0.9467, 0.985, 1.0238, 1.0678, 1.1028, 1.1411, 1.1782, 1.2127, 1.2472, 1.2778, 1.3064, 1.333, 1.3567, 1.3782, 1.3971, 1.4128, 1.4262, 1.4363, 1.4439, 1.4488, 1.4507, 1.4501, 1.4467, 1.441, 1.4326, 1.4219, 1.4091, 1.3943, 1.3771, 1.3586, 1.339, 1.3163, 1.2937, 1.271, 1.2437, 1.2188, 1.1918, 1.1642, 1.1361, 1.0811, 1.0224, 0.9628, 0.9066, 0.8512, 0.8029, 0.7478, 0.7038, 0.6493, 0.6104, 0.5704, 0.5311, 0.4949, 0.4605, 0.4301, 0.4001]
}
```
## Request Parameters
### Required
- **underlyingSymbol** `string`
The underlying ticker symbol for the options chain you wish to lookup.
### Optional
- **date** `date`
Use to lookup a historical end of day options chain from a specific trading day. If no date parameter is specified the chain will be the most current chain available during market hours. When the market is closed the chain will be from the previous session. Accepted date inputs: `ISO 8601`, `unix`, `spreadsheet`.
:::info[Historical chains carry no Greeks]
Greeks and implied volatility are not stored historically. On any request that includes `date`, the `iv`, `delta`, `gamma`, `theta` and `vega` columns are returned as `null`, on every plan. For the same reason the `delta` filter cannot be combined with `date` (the request is rejected with `400`); filter historical chains by `strike` instead.
:::
### Expiration Filters
- **expiration** `date`
- Limits the option chain to a specific expiration date. Accepted date inputs: `ISO 8601`, `unix`, `spreadsheet`.
- If omitted the next monthly expiration for real-time quotes or the next monthly expiration relative to the `date` parameter for historical quotes will be returned.
- Use the keyword `all` to return the complete option chain.
:::caution
Combining the `all` parameter with large options chains such as SPX, SPY, QQQ, etc. can cause you to consume your requests very quickly. The full SPX option chain has more than 20,000 contracts. A request is consumed for each contact you request with a price in the option chain.
:::
- **dte** `number`
Days to expiry. Limit the option chain to a single expiration date closest to the `dte` provided. Should not be used together with `from` and `to`. Take care before combining with `weekly`, `monthly`, `quarterly`, since that will limit the expirations `dte` can return. If you are using the `date` parameter, dte is relative to the `date` provided.
- **from** `date`
Limit the option chain to expiration dates after `from` (inclusive). Should be combined with `to` to create a range. Accepted date inputs: `ISO 8601`, `unix`, `spreadsheet`. If omitted all expirations will be returned.
- **to** `date`
Limit the option chain to expiration dates before `to` (inclusive). Should be combined with `from` to create a range. Accepted date inputs: `ISO 8601`, `unix`, `spreadsheet`. If omitted all expirations will be returned.
- **month** `number`
Limit the option chain to options that expire in a specific month (**1-12**).
- **year** `number`
Limit the option chain to options that expire in a specific **year**.
- **weekly** `boolean`
Limit the option chain to weekly expirations by setting weekly to `true`. If set to `false`, no weekly expirations will be returned.
- **monthly** `boolean`
Limit the option chain to standard monthly expirations by setting monthly to `true`. If set to `false`, no monthly expirations will be returned.
- **quarterly** `boolean`
Limit the option chain to quarterly expirations by setting quarterly to `true`. If set to `false`, no quarterly expirations will be returned.
> [!CAUTION]
> When combining the `weekly`, `monthly`, and `quarterly` parameters, only identical boolean values will be honored. For example, `weekly=true&monthly=false` will return an error. You must use these parameters to either include or exclude values, but you may not include and exclude at the same time. A valid use would be `monthly=true&quarterly=true` to return both monthly and quarterly expirations.
### Strike Filters
- **strike** `string`
- Limit the option chain to options with the specific strike specified. (e.g. `400`)
- Limit the option chain to a specific set of strikes (e.g. `400,405`)
- Limit the option chain to an open interval of strikes using a logical expression (e.g. `>400`)
- Limit the option chain to a closed interval of strikes by specifying both endpoints. (e.g. `400-410`)
- **delta** `number`
- Limit the option chain to a single strike closest to the `delta` provided. (e.g. `.50`)
- Limit the option chain to a specific set of deltas (e.g. `.60,.30`)
- Limit the option chain to an open interval of strikes using a logical expression (e.g. `>.50`)
- Limit the option chain to a closed interval of strikes by specifying both endpoints. (e.g. `.30-.60`)
:::tip
Filter strikes using the absolute value of the delta. The values used will always return both sides of the chain (e.g. puts & calls). This means you must filter using `side` to exclude puts or calls. Delta cannot be used to filter the side of the chain, only the strikes.
:::
:::caution[Not available on historical requests]
`delta` cannot be combined with the `date` parameter. Greeks are not stored historically, so there is no delta to filter on, and the request is rejected with `400 Bad Request`:
```json
{"s":"error","errmsg":"The 'delta' parameter is not supported on historical requests for now."}
```
Use the `strike` filter for historical chains.
:::
- **strikeLimit** `number`
Limit the number of total strikes returned by the option chain. For example, if a complete chain included 30 strikes and the limit was set to 10, the 20 strikes furthest from the money will be excluded from the response.
:::tip
If `strikeLimit` is combined with the `range` or `side` parameter, those parameters will be applied first. In the above example, if the range were set to `itm` (in the money) and side set to `call`, all puts and out of the money calls would be first excluded by the range parameter and then strikeLimit will return a maximum of 10 in the money calls that are closest to the money.
If the `side` parameter has not been used but `range` has been specified, then `strikeLimit` will return the requested number of calls and puts for each side of the chain, but duplicating the number of strikes that are received.
:::
- **range** `string`
Limit the option chain to strikes that are in the money, out of the money, at the money, or include all. If omitted all options will be returned. Valid inputs: `itm`, `otm`, `all`.
### Price/Liquidity Filters
- **minBid** `number`
Limit the option chain to options with a bid price greater than or equal to the `number` provided.
- **maxBid** `number`
Limit the option chain to options with a bid price less than or equal to the `number` provided.
- **minAsk** `number`
Limit the option chain to options with an ask price greater than or equal to the `number` provided.
- **maxAsk** `number`
Limit the option chain to options with an ask price less than or equal to the `number` provided.
- **maxBidAskSpread** `number`
Limit the option chain to options with a bid-ask spread less than or equal to the `number` provided.
- **maxBidAskSpreadPct** `number`
Limit the option chain to options with a bid-ask spread less than or equal to the `percent` provided (relative to the underlying). For example, a value of `0.5%` would exclude all options trading with a bid-ask spread greater than $1.00 in an underlying that trades at $200.
- **minOpenInterest** `number`
Limit the option chain to options with an open interest greater than or equal to the `number` provided.
- **minVolume** `number`
Limit the option chain to options with a volume transacted greater than or equal to the `number` provided.
### Other Filters
- **nonstandard** `boolean`
Include non-standard contracts by setting `nonstandard` to `true`. If set to `false`, no non-standard options expirations will be returned. If no parameter is provided, the output will default to false.
- **side** `string`
Limit the option chain to either `call` or `put`. If omitted, both sides will be returned.
- **am** `boolean`
Limit the option chain to A.M. expirations by setting `am` to `true`. If set to `false`, no A.M. expirations will be returned. This parameter is only applicable for index options such as SPX, NDX, etc. If no parameter is provided, both A.M. and P.M. expirations will be returned.
- **pm** `boolean`
Limit the option chain to P.M. expirations by setting `pm` to `true`. If set to `false`, no P.M. expirations will be returned. This parameter is only applicable for index options such as SPX, NDX, etc. If no parameter is provided, both A.M. and P.M. expirations will be returned.
> [!CAUTION]
> The `am` and `pm` parameters are only applicable for index options such as SPX, NDX, etc. If they are used for stocks or ETFs, a bad parameters error will be returned.
## Response Attributes
### Success
- **s** `string`
Status will always be `ok` when there is the quote requested.
- **optionSymbol** `array[string]`
The option symbol according to OCC symbology.
- **underlying** `array[string]`
The ticker symbol of the underlying security.
- **expiration** `array[number]`
The option's expiration date and time. Equity options expire at 4:00 PM Eastern and index options expire at 4:15 PM Eastern. All timestamps use US Eastern Time (America/New_York). See [Response Timezone](https://www.marketdata.app/docs/docs/api/dates-and-times#response-timezone) for details.
- **side** `array[string]`
The response will be `call` or `put`.
- **strike** `array[number]`
The exercise price of the option.
- **firstTraded** `array[date]`
The date the option was first traded.
- **dte** `array[number]`
The number of days until the option expires.
- **ask** `array[number]`
The ask price.
- **askSize** `array[number]`
The number of contracts offered at the ask price.
- **bid** `array[number]`
The bid price.
- **bidSize** `array[number]`
The number of contracts offered at the bid price.
- **mid** `array[number]`
The midpoint price between the ask and the bid, also known as the mark price.
- **last** `array[number]`
The last price negotiated for this option contract at the time of this quote.
- **volume** `array[number]`
The number of contracts negotiated during the trading day at the time of this quote.
- **openInterest** `array[number]`
The total number of contracts outstanding. Open interest is published once per trading day: it is calculated after the close and released at the start of the next session, and it does not change intraday. On a historical request (`date=D`), this is therefore the figure settled out of the D−1 session and available before the open on D — unlike every other field in the row, which is as of the 16:00 ET `updated` timestamp. To get the open interest produced by trading on day D, request the next trading day.
- **underlyingPrice** `array[number]`
The last price of the underlying security at the time of this quote.
- **inTheMoney** `array[booleans]`
Specifies whether the option contract was in the money true or false at the time of this quote.
- **intrinsicValue** `array[number]`
The intrinsic value of the option.
- **extrinsicValue** `array[number]`
The extrinsic value of the option.
- **updated** `array[number]`
The date and time of this quote snapshot. All timestamps use US Eastern Time (America/New_York). See [Response Timezone](https://www.marketdata.app/docs/docs/api/dates-and-times#response-timezone) for details.
> [!NOTE]
> **[Greeks on historical requests]**
>
> `iv`, `delta`, `gamma`, `theta` and `vega` are only populated on current (real-time, delayed or end-of-day) chains. On any request that includes the `date` parameter these columns are returned as `null`, on every plan, because Greeks and implied volatility are not stored historically.
- **iv** `array[number]`
The [implied volatility](https://www.investopedia.com/terms/i/iv.asp) of the option.
- **delta** `array[number]`
The [delta](https://www.investopedia.com/terms/d/delta.asp) of the option.
- **gamma** `array[number]`
The [gamma](https://www.investopedia.com/terms/g/gamma.asp) of the option.
- **theta** `array[number]`
The [theta](https://www.investopedia.com/terms/t/theta.asp) of the option.
- **vega** `array[number]`
The [vega](https://www.investopedia.com/terms/v/vega.asp) of the option.
### No Data
- **s** `string`
Status will be `no_data` if no candles are found for the request.
- **nextTime** `number` optional
Unix time of the next quote if there is no data in the requested period, but there is data in a subsequent period.
- **prevTime** `number` optional
Unix time of the previous quote if there is no data in the requested period, but there is data in a previous period.
### Error
- **s** `string`
Status will be `error` if the request produces an error response.
- **errmsg** `string`
An error message.
## Usage Information
### Data Availability
The type of option chain data you receive depends on your user type and OPRA entitlement. This may include real-time data, 15-minute delayed data, or historical data, depending on the plan or access level. To get real-time options data, users need to sign the OPRA agreement. [Learn more about entitlements](https://www.marketdata.app/docs/account/entitlements).
| User Type | OPRA Entitlement | Data Type |
|------------------|------------------|------------------------|
| Non-Professional | ✅ | Real-time |
| Non-Professional | ❌ | 15-min delayed |
| Professional | Any | Historical (1 day old) |
| Unknown | Any | Historical (1 day old) |
### Point-in-Time Semantics
One historical row does not carry one as-of time. On a `date=D` request:
| Field | As of |
|----------------------------------------------------------|----------------------------------------------|
| `openInterest` | before the open on D, settled out of D−1 |
| `bid`, `ask`, `mid`, `last`, `underlyingPrice`, `volume` | the row's `updated` timestamp, 16:00 ET on D |
| `iv`, `delta`, `gamma`, `theta`, `vega` | not returned — see the note on `date` above |
Two consequences matter when you build a point-in-time dataset:
- `openInterest` carries no look-ahead. It was published before trading on D began, so it is safe to use at any time on D. To read the open interest that D's trading produced, request the next trading day.
- `volume` does carry look-ahead relative to an intraday cutoff. It is the full-session total for D, not the volume up to a given moment.
### Pricing
The cost of using the option chain API endpoint depends on the type of data you choose and your usage pattern. Here's a breakdown of the pricing:
| Data Type | Cost Basis | Credits Required per Unit |
|------------------|-------------------------|---------------------------|
| Real-Time Data | Per option symbol | 1 credit |
| 15m Delayed Data | Per option symbol | 1 credit |
| Cached Data | Per API call | 1 credit |
| Historical Data | Per 1000 option symbols | 1 credit |
#### Examples
1. **Real-Time Usage**
- If you query all strikes and all expirations for SPX (which has 22,718 total option contracts) in real-time, it will cost you 22,718 credits.
2. **15-minute Delayed Usage**
- If you query all strikes and all expirations for SPX (which has 22,718 total option contracts) and opt for 15m delayed data, it will cost you 22,718 credits.
3. **Cached Mode Usage**
- A single API call to SPX using the Cached Mode, regardless of the number of option symbols queried, will cost you 1 credit.
4. **Historical Data Usage**
- When including the `date` parameter for historical queries, the cost is 1 credit per 1000 option symbols returned in the response.
- For example, if a historical query returns 5,000 option symbols, it will cost 5 credits (5,000 ÷ 1000 = 5).
---
# Option Expirations
Source: https://www.marketdata.app/docs/api/options/expirations/index.md
Get a list of current or historical option expiration dates for an underlying symbol. If no optional parameters are used, the endpoint returns all expiration dates in the option chain.
## Endpoint
```
https://api.marketdata.app/v1/options/expirations/{underlyingSymbol}/
```
### Method
```
GET
```
## Request Example
### HTTP
**GET** [https://api.marketdata.app/v1/options/expirations/AAPL/](https://api.marketdata.app/v1/options/expirations/AAPL/)
### JavaScript
```js title="app.js"
import { MarketDataClient } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const data = await client.options.expirations("AAPL");
console.log(`Updated: ${data.updated}`);
for (const exp of data.expirations) {
console.log(exp);
}
} catch (error) {
console.error(error);
}
```
### TypeScript
```typescript title="app.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { OptionsExpirationsResponse } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const data: OptionsExpirationsResponse = await client.options.expirations("AAPL");
console.log(`Updated: ${data.updated}`);
for (const exp of data.expirations) {
console.log(exp);
}
} catch (error) {
console.error(error);
}
```
### Python
```python title="app.py"
from marketdata import MarketDataClient
client = MarketDataClient()
expirations = client.options.expirations("AAPL")
print(expirations)
```
### Go
```go title="optionExpirations.go"
import (
"fmt"
api "github.com/MarketDataApp/sdk-go"
)
func ExampleOptionsExpirationsRequest() {
expirations, err := OptionsExpirations().UnderlyingSymbol("AAPL").Get()
if err != nil {
fmt.Print(err)
return
}
for _, expiration := range expirations {
fmt.Println(expiration)
}
}
```
### PHP
```php title="optionExpirations.php"
use MarketDataApp\Client;
$client = new Client();
$expirations = $client->options->expirations("AAPL");
// Display all expiration dates
echo $expirations;
```
### Java
```java title="OptionExpirations.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.options.OptionsExpirationsRequest;
try (MarketDataClient client = new MarketDataClient()) {
client.options().expirations(OptionsExpirationsRequest.of("AAPL")).values().forEach(System.out::println);
}
```
### Kotlin
```kotlin title="OptionExpirations.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.options.OptionsExpirationsRequest
MarketDataClient().use { client ->
client.options().expirations(OptionsExpirationsRequest.of("AAPL")).values().forEach(::println)
}
```
### C#
```csharp title="OptionExpirations.cs"
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
foreach (var expiration in (await client.Options.GetExpirationsAsync("AAPL")).Values)
{
Console.WriteLine(expiration);
}
```
## Response Example
```json
{
"s": "ok",
"expirations": [
"2022-09-23",
"2022-09-30",
"2022-10-07",
"2022-10-14",
"2022-10-21",
"2022-10-28",
"2022-11-18",
"2022-12-16",
"2023-01-20",
"2023-02-17",
"2023-03-17",
"2023-04-21",
"2023-06-16",
"2023-07-21",
"2023-09-15",
"2024-01-19",
"2024-06-21",
"2025-01-17"
],
"updated": 1663704000
}
```
## Request Parameters
### Required
- **underlyingSymbol** `string`
The underlying ticker symbol for the options chain you wish to lookup.
### Optional
- **strike** `number`
Limit the lookup of expiration dates to the strike provided. This will cause the endpoint to only return expiration dates that include this strike.
- **date** `date`
Use to lookup a historical list of expiration dates from a specific previous trading day. If date is omitted the expiration dates will be from the current trading day during market hours or from the last trading day when the market is closed. Accepted date inputs: `ISO 8601`, `unix`, `spreadsheet`.
## Response Attributes
### Success
- **s** `string`
Status will always be `ok` when there is strike
data for the underlying/expirations requested.
- **expirations** `array[date]`
The expiration dates requested for the underlying with the option strikes for each expiration.
- **updated** `date`
The date and time of this list of options expirations was last updated. All timestamps use US Eastern Time (America/New_York). For historical expirations, this should match the `date` parameter. See [Response Timezone](https://www.marketdata.app/docs/docs/api/dates-and-times#response-timezone) for details.
### No Data
- **s** `string`
Status will be `no_data` if no data is found for the request.
- **nextTime** `number` optional
Unix time of the next quote if there is no data in the requested period, but there is data in a subsequent period.
- **prevTime** `number` optional
Unix time of the previous quote if there is no data in the requested period, but there is data in a previous period.
### Error
- **s** `string`
Status will be `error` if the request produces an error response.
- **errmsg** `string`
An error message.
## Usage Information
### Data Availability
This endpoint provides options expiration dates data. This endpoint does not require any exchange entitlements and is available to all users.
| User Type | Exchange Entitlement | Data Type |
|-----------|----------------------|--------------|
| All Users | Not Required | Options Data |
### Pricing
The cost of using the expirations API endpoint is 1 credit per API call.
| Data Type | Cost Basis | Credits Required per Unit |
|------------------|--------------|---------------------------|
| Expirations Data | Per API call | 1 credit |
---
# Option Lookup
Source: https://www.marketdata.app/docs/api/options/lookup/index.md
Generate a properly formatted OCC option symbol based on the user's human-readable description of an option. This endpoint converts text such as "AAPL 12/17/27 $250 Call" to OCC option symbol format: AAPL271217C00250000. The user input must be URL-encoded.
## Endpoint
```
https://api.marketdata.app/v1/options/lookup/{userInput}/
```
### Method
```
GET
```
## Request Example
### HTTP
**GET** [https://api.marketdata.app/v1/options/lookup/AAPL%2012/17/2027%20250%20Call/](https://api.marketdata.app/v1/options/lookup/AAPL%2012/17/2027%20250%20Call/)
### JavaScript
```js title="app.js"
import { MarketDataClient } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const data = await client.options.lookup("AAPL 12/17/2027 250 Call");
console.log(data.optionSymbol); // "AAPL271217C00250000"
} catch (error) {
console.error(error);
}
```
### TypeScript
```typescript title="app.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { OptionsLookupResponse } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const data: OptionsLookupResponse = await client.options.lookup("AAPL 12/17/2027 250 Call");
console.log(data.optionSymbol); // "AAPL271217C00250000"
} catch (error) {
console.error(error);
}
```
### Python
```python title="app.py"
from marketdata import MarketDataClient
client = MarketDataClient()
lookup = client.options.lookup("AAPL 12/17/2027 250 Call")
print(lookup)
```
### Go
```go title="optionLookup.go"
import (
"fmt"
api "github.com/MarketDataApp/sdk-go"
)
func ExampleOptionLookupRequest() {
optionSymbol, err := OptionLookup().UserInput("AAPL 12/17/2027 250 Call").Get()
if err != nil {
fmt.Print(err)
return
}
fmt.Println(optionSymbol)
}
```
### PHP
```php title="optionLookup.php"
use MarketDataApp\Client;
$client = new Client();
$lookup = $client->options->lookup("AAPL 12/17/2027 250 Call");
// Display the OCC symbol
echo $lookup;
```
### Java
```java title="OptionLookup.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.options.OptionsLookupRequest;
try (MarketDataClient client = new MarketDataClient()) {
System.out.println(client.options().lookup(OptionsLookupRequest.of("AAPL 12/17/2027 250 Call")).values()); // "AAPL271217C00250000"
}
```
### Kotlin
```kotlin title="OptionLookup.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.options.OptionsLookupRequest
MarketDataClient().use { client ->
val optionSymbol =
client.options().lookup(OptionsLookupRequest.of("AAPL 12/17/2027 250 Call")).values()
println(optionSymbol) // "AAPL271217C00250000"
}
```
### C#
```csharp title="OptionLookup.cs"
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
var lookup = await client.Options.GetLookupAsync("AAPL 12/17/2027 250 Call");
Console.WriteLine(lookup.Values); // "AAPL271217C00250000"
```
## Response Example
```json
{
"s": "ok",
"optionSymbol": "AAPL271217C00250000"
}
```
## Request Parameters
### Required
- **userInput** `string`
The human-readable string input that contains (1) stock symbol (2) strike (3) expiration date (4) option side (i.e. put or call). This endpoint will translate the user's input into a valid OCC option symbol.
## Response Attributes
### Success
- **s** `string`
Status will always be `ok` when the OCC option symbol is successfully generated.
- **optionSymbol** `string`
The generated OCC option symbol based on the user's input.
### Error
- **s** `string`
Status will be `error` if the request produces an error response.
- **errmsg** `string`
An error message.
## Notes
- This endpoint will return an error if the option symbol that would be formed by the user's input does not exist.
## Usage Information
### Data Availability
This endpoint provides option symbol lookup functionality. This endpoint does not require any exchange entitlements and is available to all users.
| User Type | Exchange Entitlement | Data Type |
|-----------|----------------------|--------------|
| All Users | Not Required | Utility Data |
### Pricing
The cost of using the lookup API endpoint is 1 credit per API call.
| Data Type | Cost Basis | Credits Required per Unit |
|-------------|--------------|---------------------------|
| Lookup Data | Per API call | 1 credit |
---
# Option Quotes
Source: https://www.marketdata.app/docs/api/options/quotes/index.md
Get a current or historical end of day quote for a single options contract.
## Endpoint
```
https://api.marketdata.app/v1/options/quotes/{optionSymbol}/
```
### Method
```
GET
```
## Request Example
### HTTP
**GET** [https://api.marketdata.app/v1/options/quotes/AAPL271217C00250000/](https://api.marketdata.app/v1/options/quotes/AAPL271217C00250000/)
### JavaScript
```js title="app.js"
import { MarketDataClient } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const quotes = await client.options.quotes("AAPL271217C00250000");
const q = quotes[0];
console.log(
`${q.optionSymbol}: bid=${q.bid} ask=${q.ask} delta=${q.delta}`
);
} catch (error) {
console.error(error);
}
```
### TypeScript
```typescript title="app.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { OptionsQuotes } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const quotes: OptionsQuotes[] = await client.options.quotes("AAPL271217C00250000");
const q = quotes[0];
console.log(
`${q.optionSymbol}: bid=${q.bid} ask=${q.ask} delta=${q.delta}`
);
} catch (error) {
console.error(error);
}
```
### Python
```python title="app.py"
from marketdata import MarketDataClient
client = MarketDataClient()
quotes = client.options.quotes("AAPL271217C00250000")
print(quotes)
```
### Go
```go title="optionQuotes.go"
import (
"fmt"
api "github.com/MarketDataApp/sdk-go"
)
func ExampleOptionQuoteRequest() {
quotes, err := OptionQuote().OptionSymbol("AAPL271217C00250000").Get()
if err != nil {
fmt.Print(err)
return
}
for _, quote := range quotes {
fmt.Println(quote)
}
}
```
### PHP
```php title="optionQuotes.php"
use MarketDataApp\Client;
$client = new Client();
$quotes = $client->options->quotes("AAPL271217C00250000");
// Display formatted quotes with Greeks
echo $quotes;
```
### Java
```java title="OptionQuotes.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.options.OptionsQuoteRequest;
try (MarketDataClient client = new MarketDataClient()) {
client.options().quote(OptionsQuoteRequest.of("AAPL271217C00250000")).values().forEach(System.out::println);
}
```
### Kotlin
```kotlin title="OptionQuotes.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.options.OptionsQuoteRequest
MarketDataClient().use { client ->
client.options().quote(OptionsQuoteRequest.of("AAPL271217C00250000")).values().forEach(::println)
}
```
### C#
```csharp title="OptionQuotes.cs"
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
foreach (var quote in (await client.Options.GetQuoteAsync("AAPL271217C00250000")).Values)
{
Console.WriteLine(quote);
}
```
## Response Example
```json
{
"s": "ok",
"optionSymbol": ["AAPL271217C00250000"],
"ask": [5.25],
"askSize": [57],
"bid": [5.15],
"bidSize": [994],
"mid": [5.2],
"last": [5.25],
"volume": [977],
"openInterest": [61289],
"underlyingPrice": [136.12],
"inTheMoney": [false],
"updated": [1665673292],
"iv": [0.3468],
"delta": [0.347],
"gamma": [0.015],
"theta": [-0.05],
"vega": [0.264],
"intrinsicValue": [13.88],
"extrinsicValue": [8.68]
}
```
## Request Parameters
### Required
- **optionSymbol** `string`
The option symbol (as defined by the OCC) for the option you wish to lookup. Use the current OCC option symbol format, even for historic options that quoted before the format change in 2010.
### Optional
- **date** `date`
Use to lookup a historical end of day quote from a specific trading day. If no date is specified the quote will be the most current price available during market hours. When the market is closed the quote will be from the last trading day. Accepted date inputs: `ISO 8601`, `unix`, `spreadsheet`.
:::info[Historical quotes carry no Greeks]
Greeks and implied volatility are not stored historically. On any request that includes `date`, `from` or `to`, the `iv`, `delta`, `gamma`, `theta` and `vega` columns are returned as `null`, on every plan.
:::
- **from** `date`
Use to lookup a series of end of day quotes. From is the oldest (leftmost) date to return (inclusive). If from/to is not specified the quote will be the most current price available during market hours. When the market is closed the quote will be from the last trading day. Accepted date inputs: `ISO 8601`, `unix`, `spreadsheet`.
- **to** `date`
Use to lookup a series of end of day quotes. From is the newest (rightmost) date to return (exclusive). If from/to is not specified the quote will be the most current price available during market hours. When the market is closed the quote will be from the last trading day. Accepted date inputs: `ISO 8601`, `unix`, `spreadsheet`.
## Response Attributes
### Success
- **s** `string`
Status will always be `ok` when there is data for the quote requested.
- **optionSymbol** `array[string]`
The option symbol according to OCC symbology.
- **underlying** `array[string]`
The ticker symbol of the underlying security.
- **expiration** `array[number]`
The option's expiration date and time. Equity options expire at 4:00 PM Eastern and index options expire at 4:15 PM Eastern. All timestamps use US Eastern Time (America/New_York). See [Response Timezone](https://www.marketdata.app/docs/docs/api/dates-and-times#response-timezone) for details.
- **side** `array[string]`
The response will be `call` or `put`.
- **strike** `array[number]`
The exercise price of the option.
- **firstTraded** `array[date]`
The date the option was first traded.
- **dte** `array[number]`
The number of days until the option expires.
- **ask** `array[number]`
The ask price.
- **askSize** `array[number]`
The number of contracts offered at the ask price.
- **bid** `array[number]`
The bid price.
- **bidSize** `array[number]`
The number of contracts offered at the bid price.
- **mid** `array[number]`
The midpoint price between the ask and the bid, also known as the mark price.
- **last** `array[number]`
The last price negotiated for this option contract at the time of this quote.
- **volume** `array[number]`
The number of contracts negotiated during the trading day at the time of this quote.
- **openInterest** `array[number]`
The total number of contracts outstanding. Open interest is published once per trading day: it is calculated after the close and released at the start of the next session, and it does not change intraday. On a historical request (`date=D`, or a `from`/`to` series), this is therefore the figure settled out of the D−1 session and available before the open on D — unlike every other field in the row, which is as of the 16:00 ET `updated` timestamp. To get the open interest produced by trading on day D, request the next trading day.
- **underlyingPrice** `array[number]`
The last price of the underlying security at the time of this quote.
- **inTheMoney** `array[booleans]`
Specifies whether the option contract was in the money true or false at the time of this quote.
- **intrinsicValue** `array[number]`
The instrinisc value of the option.
- **extrnisicValue** `array[number]`
The extrinsic value of the option.
- **updated** `array[number]`
The date and time of this quote snapshot. All timestamps use US Eastern Time (America/New_York). See [Response Timezone](https://www.marketdata.app/docs/docs/api/dates-and-times#response-timezone) for details.
> [!NOTE]
> **[Greeks on historical requests]**
>
> `iv`, `delta`, `gamma`, `theta` and `vega` are only populated on current (real-time, delayed or end-of-day) quotes. On any request that includes `date`, `from` or `to` these columns are returned as `null`, on every plan, because Greeks and implied volatility are not stored historically.
- **iv** `array[number]`
The [implied volatility](https://www.investopedia.com/terms/i/iv.asp) of the option.
- **delta** `array[number]`
The [delta](https://www.investopedia.com/terms/d/delta.asp) of the option.
- **gamma** `array[number]`
The [gamma](https://www.investopedia.com/terms/g/gamma.asp) of the option.
- **theta** `array[number]`
The [theta](https://www.investopedia.com/terms/t/theta.asp) of the option.
- **vega** `array[number]`
The [vega](https://www.investopedia.com/terms/v/vega.asp) of the option.
### No Data
Returned when the request is valid, but **no quote data exists** for the requested symbol or time period.
This response corresponds to an **HTTP 404 Not Found** status.
- **s** `string`
Will be `no_data`.
- **errmsg** `string`
A descriptive message explaining why no data is available.
For example:
- `"No option found. No option was found for this strike and expiration."`
This occurs when even though the option symbol format is valid, no real, tradable contract exists for the specified strike and expiration.
A `no_data` response may occur when:
- The market is closed (weekends or holidays)
- The symbol or option contract had no trading quotes on the requested date
- The contract had not begun trading yet
- No option exists for the requested strike and expiration
Even when the option symbol format is valid, the absence of a real, tradable contract or any quote data results in a `404` / `no_data` response.
### Error
Returned when the request itself is invalid or cannot be processed.
- **s** `string`
Will be `error`.
- **errmsg** `string`
A human-readable description of the error.
These cases correspond to **HTTP 400 Bad Request**, such as:
- Invalid or malformed option symbol format
- Parameters that cannot be parsed
- Unsupported or invalid values
Use the HTTP status code—not just the `s` field—to distinguish between invalid input (`400`) and valid requests that simply have no data (`404` via `no_data`).
## Usage Information
### Data Availability
The type of option quote data you receive depends on your user type and OPRA entitlement. This may include real-time data, 15-minute delayed data, or historical data, depending on the plan or access level. To get real-time options data, users need to sign the OPRA agreement. [Learn more about entitlements](https://www.marketdata.app/docs/account/entitlements).
| User Type | OPRA Entitlement | Data Type |
|------------------|------------------|------------------------|
| Non-Professional | ✅ | Real-time |
| Non-Professional | ❌ | 15-min delayed |
| Professional | Any | Historical (1 day old) |
| Unknown | Any | Historical (1 day old) |
### Pricing
The cost of using the option quote API endpoint depends on the type of data you choose and your usage pattern. Here's a breakdown of the pricing:
| Data Type | Cost Basis | Credits Required per Unit |
|------------------|-------------------|---------------------------|
| Real-Time Data | Per option symbol | 1 credit |
| 15m Delayed Data | Per option symbol | 1 credit |
| Historical Data | Per 1000 quotes | 1 credit |
---
# Rate Limits
Source: https://www.marketdata.app/docs/api/rate-limiting/index.md
We enforce rate limits to ensure our API remains accessible and efficient for all users. We have two types of limits: API credits (total credits per unit of time) and a concurrent request limit (simultaneous requests).
For troubleshooting, use:
- [429: Credit Limit Reached](https://www.marketdata.app/docs/api/troubleshooting/running-out-of-credits)
- [429: Too Many Concurrent Requests](https://www.marketdata.app/docs/api/troubleshooting/too-many-concurrent-requests)
## API Credits
Normally each API call consumes a single credit. However, **if the response includes more than a single symbol, it can consume multiple credits**. Often, users can navigate around a rate limit by making the most of the diverse filters we provide (e.g. instead of retrieving an entire option chain, apply specific filters to narrow down the results).
**Any single API request is permitted as long as you have at least 1 credit remaining**, even if the request will consume more credits than your current balance. After such a request, your balance may go negative and further requests will return `429` responses until the credit window resets.
### Usage Counter Reset Time
The usage counter for all plans with a daily limit resets at **9:30 AM Eastern Time** (NYSE opening bell). This reset timing is crucial for users to understand so they can plan their API usage efficiently without hitting the rate limit unexpectedly.
This reset time is also a safety control: if a script runs out of control overnight, a 9:30 AM reset limits impact to one trading day budget instead of potentially consuming two trading-day budgets across midnight.
> [!TIP]
> **[Managing Timezone Changes]**
>
> To handle the reset time accurately regardless of your local timezone, it's recommended to use the `America/New_York` timezone identifier. This ensures that your application adjusts for any changes in Eastern Time, including daylight saving shifts, automatically.
>
> By aligning your application's timing functions with the `America/New_York` timezone, you can ensure that your usage of the API remains within the allocated rate limits, taking into account the precise reset timing at 9:30 AM Eastern Time.
## Concurrent Request Limit
To maintain the stability and performance of our API, we enforce a limit of no more than 50 concurrent requests across all subscription plans. This means that at any given time, you should not have more than 50 active API calls in progress. Requests in excess of the concurrency limit will generate 429 responses.
To adhere to this limit, it is advisable to implement a worker or thread pool mechanism in your application that does not exceed 50 workers. Each worker should handle no more than one API request at a time. This setup helps in efficiently managing API calls without breaching the concurrent request limit and ensures fair usage among all users.
## Rate Limits By Plan
Different plans have specific rate limits. Free/Starter/Trader use daily limits, while Quant/Prime use per-minute limits.
| | Free Forever | Starter | Trader | Quant | Prime |
|--------------------------|--------------|----------|----------|----------|----------|
| Daily API Credits | 100 | 10,000 | 100,000 | No Limit | No Limit |
| Per Minute API Credits | No Limit | No Limit | No Limit | 10,000 | 100,000 |
| Concurrent Request Limit | 50 | 50 | 50 | 50 | 50 |
### Summary
- **Free Forever Plan:** 100 credits per day.
- **Starter Plan:** 10,000 credits per day.
- **Trader Plan:** 100,000 credits per day.
- **Quant Plan:** 10,000 credits per minute.
- **Prime Plan:** 100,000 credits per minute.
## Credits
Each time you call the API, the system increases your credits counter. Normally each successful response consumes 1 credit. However, **if you request multiple symbols in a single API call using `stocks/quotes`, `stocks/prices`, `stocks/bulkcandles`, or `options/chain`, credits are consumed per symbol included in the response**.
> [!CAUTION]
> For users working with options, take care before repeatedly requesting quotes for an entire option chain. **Each option symbol included in the response consumes credits**. If you download the entire SPX option chain (which has 20,000+ option symbols), you can exhaust your credit limit very quickly. Use our option chain filters to request only the strikes and expirations you need.
## Headers to Manage the Rate Limit
We provide the following headers in our responses to help you manage the rate limit and throttle your applications when necessary:
- `X-Api-Ratelimit-Limit`: The maximum number of API credits permitted (per day for Free/Starter/Trader plans or per minute for Quant/Prime users).
- `X-Api-Ratelimit-Remaining`: The number of API credits remaining in the current rate day/period.
- `X-Api-Ratelimit-Reset`: The time at which the current rate limit window resets in UTC epoch seconds.
- `X-Api-Ratelimit-Consumed`: The quantity of API credits consumed by the current request.
## Detailed Rate Limit Rules
- Each successful response increases the counter by a minimum of 1 credit.
- Only status 200/203 responses consume credits.
- NULL responses are not counted.
- Error responses are not counted.
- Requests consume more than 1 credit if the response includes prices for more than one symbol (for example, `options/chain` or `stocks/quotes` bulk requests).
- Responses that include more than one symbol, but do not include the **bid**, **ask**, **mid**, or **last** columns _**do not**_ consume multiple credits and are counted as a single credit.
- Certain free trial symbols like AAPL stock, AAPL options, and the VFINX mutual fund do not consume credits.
## Strategies To Avoid Rate Limiting
- Exclude the bid, ask, mid, and last columns from your option chain requests if the current price is not needed.
- Use the extensive option chain filters such as `strikeLimit` to exclude unnecessary strikes from your requests.
- Paying customers can make use of the reduced-price cached mode. Use the `mode=cached` parameter on supported bulk endpoints to retrieve previously cached quotes instead of making a live request. This can save thousands of credits. For more details, refer to the [mode parameter documentation](https://www.marketdata.app/docs/api/universal-parameters/mode).
> [!NOTE]
> **[Trial plans]**
>
> `mode=cached` is not available on Starter Trial, Trader Trial, and Prime Trial plans.
---
# Stocks
Source: https://www.marketdata.app/docs/api/stocks/index.md
Stock endpoints include numerous fundamental, technical, and pricing data.
## Root Endpoint For Stocks
```
https://api.marketdata.app/v1/stocks/
```
## Stocks Endpoints
---
# Bulk Stock Candles
Source: https://www.marketdata.app/docs/api/stocks/bulkcandles/index.md
Get bulk historical candle data for stocks. This endpoint returns bulk daily candle data for multiple stocks. Unlike the standard candles endpoint, this endpoint returns a single daily for each symbol provided.
## Endpoint
```
https://api.marketdata.app/v1/stocks/bulkcandles/{resolution}/
```
### Method
```
GET
```
## Request Example
### HTTP
**GET** [https://api.marketdata.app/v1/stocks/bulkcandles/D/?symbols=AAPL,META,MSFT](https://api.marketdata.app/v1/stocks/bulkcandles/D/?symbols=AAPL,META,MSFT)
### JavaScript
> [!NOTE]
> The JavaScript/TypeScript SDK does not yet provide a method for the bulk candles endpoint, so these examples use the raw `fetch()` API. They will be replaced with `@marketdata/sdk` examples once bulk support is added.
```js title="app.js"
fetch(
"https://api.marketdata.app/v1/stocks/bulkcandles/D/?symbols=AAPL,META,MSFT"
)
.then((res) => res.json())
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
```
### TypeScript
> [!NOTE]
> The JavaScript/TypeScript SDK does not yet provide a method for the bulk candles endpoint, so these examples use the raw `fetch()` API. They will be replaced with `@marketdata/sdk` examples once bulk support is added.
```typescript title="app.ts"
interface BulkCandlesResponse {
s: string;
symbol: string[];
o: number[];
h: number[];
l: number[];
c: number[];
v: number[];
t: number[];
}
const response: Response = await fetch(
"https://api.marketdata.app/v1/stocks/bulkcandles/D/?symbols=AAPL,META,MSFT"
);
const data: BulkCandlesResponse = await response.json();
console.log(data);
```
### Python
```python title="app.py"
import requests
url = "https://api.marketdata.app/v1/stocks/bulkcandles/D/?symbols=AAPL,META,MSFT"
response = requests.request("GET", url)
print(response.text)
```
### Go
```go title="bulkStockCandles.go"
import (
"fmt"
api "github.com/MarketDataApp/sdk-go"
)
func ExampleBulkStockCandlesRequest_get() {
symbols := []string{"AAPL", "META", "MSFT"}
candles, err := BulkStockCandles().Resolution("D").Symbols(symbols).Get()
if err != nil {
fmt.Print(err)
return
}
for _, candle := range candles {
fmt.Println(candle)
}
}
```
### PHP
```php title="bulkStockCandles.php"
use MarketDataApp\Client;
$client = new Client();
$candles = $client->stocks->bulkCandles(
symbols: ["AAPL", "META", "MSFT"],
resolution: "D"
);
// Display formatted candles summary
echo $candles;
```
### Java
> [!NOTE]
> The Java SDK does not yet provide a method for the bulk candles endpoint, so this example uses the JDK's `java.net.http.HttpClient` directly. It will be replaced with a `marketdata-sdk-java` example once bulk support is added.
```java title="BulkStockCandles.java"
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient http = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.marketdata.app/v1/stocks/bulkcandles/D/?symbols=AAPL,META,MSFT"))
.header("Authorization", "Bearer " + System.getenv("MARKETDATA_TOKEN"))
.GET()
.build();
HttpResponse response = http.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```
### Kotlin
> [!NOTE]
> The Java SDK does not yet provide a method for the bulk candles endpoint, so this example uses the JDK's `java.net.http.HttpClient` directly. It will be replaced with a `marketdata-sdk-java` example once bulk support is added.
```kotlin title="BulkStockCandles.kt"
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
val http = HttpClient.newHttpClient()
val request = HttpRequest.newBuilder()
.uri(URI.create("https://api.marketdata.app/v1/stocks/bulkcandles/D/?symbols=AAPL,META,MSFT"))
.header("Authorization", "Bearer ${System.getenv("MARKETDATA_TOKEN")}")
.GET()
.build()
val response = http.send(request, HttpResponse.BodyHandlers.ofString())
println(response.body())
```
### C#
> [!NOTE]
> The C# SDK does not yet provide a method for the bulk candles endpoint, so this example uses `System.Net.Http.HttpClient` directly. It will be replaced with a `MarketDataApp` SDK example once bulk support is added.
```csharp title="BulkStockCandles.cs"
using System.Net.Http.Headers;
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer",
Environment.GetEnvironmentVariable("MARKETDATA_TOKEN"));
var response = await http.GetAsync("https://api.marketdata.app/v1/stocks/bulkcandles/D/?symbols=AAPL,META,MSFT");
Console.WriteLine(await response.Content.ReadAsStringAsync());
```
## Response Example
```json
{
"s": "ok",
"symbol": ["AAPL", "META", "MSFT"],
"o": [196.16, 345.58, 371.49],
"h": [196.95, 353.6, 373.26],
"l": [195.89, 345.12, 369.84],
"c": [196.94, 350.36, 373.26],
"v": [40714051, 17729362, 20593658],
"t": [1703048400,1703048400,1703048400]
}
```
## Request Parameters
### Required
- **resolution** `string`
The duration of each candle. Only daily candles are supported at this time.
- Daily Resolutions: (`daily`, `D`, `1D`)
- **symbols** `string`
The ticker symbols to return in the response, separated by commas. The symbols parameter may be omitted if the `snapshot` parameter is set to `true`.
> [!TIP]
> **[Batching multi-symbol requests]**
>
> There is no application-level cap on the number of symbols, but the HTTP request line is limited to 4094 bytes. For typical 4-character tickers that's roughly 700–800 symbols per request. To stay clear of the limit, chunk into batches of **500 symbols or fewer**. See [400: Bad Request — URL Too Long](https://www.marketdata.app/docs/api/troubleshooting/bad-request#case-5-url-too-long-multi-symbol-requests) for details.
### Optional
- **snapshot** `boolean`
Returns candles for all available symbols for the date indicated. The `symbols` parameter can be omitted if `snapshot` is set to true.
- **date** `date`
The date of the candles to be returned. If no date is specified, during market hours the candles returned will be from the current session. If the market is closed the candles will be from the most recent session. Accepted date inputs: `ISO 8601`, `unix`, `spreadsheet`.
- **adjustsplits** `boolean`
Adjust historical data for for historical splits and reverse splits. Market Data uses the CRSP methodology for adjustment.
Daily candles default: `true`.
## Response Attributes
### Success
- **s** `string`
Will always be `ok` when there is data for the candles requested.
- **symbol** `string`
The ticker symbol of the stock.
- **o** `array[number]`
Open price.
- **h** `array[number]`
High price.
- **l** `array[number]`
Low price.
- **c** `array[number]`
Close price.
- **v** `array[number]`
Volume.
- **t** `array[number]`
Candle time. This is midnight US Eastern Time on the session date. All timestamps use US Eastern Time (America/New_York). See [Response Timezone](https://www.marketdata.app/docs/docs/api/dates-and-times#response-timezone) for details.
### No Data
- **s** `string`
Status will be `no_data` if no candles are found for the request.
### Error
- **s** `string`
Status will be `error` if the request produces an error response.
- **errmsg** `string`
An error message.
## Usage Information
### Data Availability
**Real-time data is not available for this endpoint under any plan or entitlement.** The type of candle data you receive depends on your user type and UTP entitlement. This may include 15-minute delayed candles or historical candles (1 day old), depending on the plan or access level.
| User Type | UTP Entitlement | Candle Type |
|------------------|-----------------|------------------------|
| Non-Professional | ✅ | 15-min delayed |
| Non-Professional | ❌ | Historical (1 day old) |
| Professional | Any | Historical (1 day old) |
| Unknown | Any | Historical (1 day old) |
> [!NOTE]
> **[What are entitlements?]**
>
> Entitlements are permissions granted by exchanges that allow access to their data. To get 15-minute delayed candles, non-professional users need to sign the [UTP agreement](https://www.marketdata.app/docs/account/entitlements). [Learn more about entitlements](https://www.marketdata.app/docs/account/entitlements).
### Pricing
The cost of using the bulk candles API endpoint is 1 credit per symbol returned in the response.
| Data Type | Cost Basis | Credits Required per Unit |
|------------------------|---------------|---------------------------|
| 15-minute Delayed Data | Per symbol | 1 credit |
| Historical Data | Per symbol | 1 credit |
| Real-Time Data | Not available | N/A |
---
# Stock Candles
Source: https://www.marketdata.app/docs/api/stocks/candles/index.md
Get historical price candles for stocks or ETFs.
## Endpoint
```
https://api.marketdata.app/v1/stocks/candles/{resolution}/{symbol}/
```
### Method
```
GET
```
## Request Example
### HTTP
**GET** [https://api.marketdata.app/v1/stocks/candles/D/AAPL/?from=2024-01-01&to=2024-01-31](https://api.marketdata.app/v1/stocks/candles/D/AAPL/?from=2024-01-01&to=2024-01-31)
### JavaScript
```js title="app.js"
import { MarketDataClient } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const candles = await client.stocks.candles("AAPL", {
resolution: "D",
from: "2024-01-01",
to: "2024-01-31",
});
for (const c of candles) {
console.log(`t=${c.t} o=${c.o} h=${c.h} l=${c.l} c=${c.c} v=${c.v}`);
}
} catch (error) {
console.error(error);
}
```
### TypeScript
```typescript title="app.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { StockCandle } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const candles: StockCandle[] = await client.stocks.candles("AAPL", {
resolution: "D",
from: "2024-01-01",
to: "2024-01-31",
});
for (const c of candles) {
console.log(`t=${c.t} o=${c.o} h=${c.h} l=${c.l} c=${c.c} v=${c.v}`);
}
} catch (error) {
console.error(error);
}
```
### Python
```python title="app.py"
from marketdata import MarketDataClient
client = MarketDataClient()
candles = client.stocks.candles("AAPL", resolution="D", from_date="2024-01-01", to_date="2024-01-31")
print(candles)
```
### Go
```go title="stockCandles.go"
import (
"fmt"
api "github.com/MarketDataApp/sdk-go"
)
func ExampleStockCandlesRequest() {
candles, err := StockCandles().Resolution("D").Symbol("AAPL").From("2024-01-01").To("2024-01-31").Get()
if err != nil {
fmt.Print(err)
return
}
for _, candle := range candles {
fmt.Println(candle)
}
}
```
### PHP
```php title="stockCandles.php"
use MarketDataApp\Client;
$client = new Client();
$candles = $client->stocks->candles(
symbol: "AAPL",
from: "2024-01-01",
to: "2024-01-31",
resolution: "D"
);
// Display formatted candles summary
echo $candles;
```
### Java
```java title="StockCandles.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.stocks.StockCandlesRequest;
import com.marketdata.sdk.stocks.StockResolution;
import java.time.LocalDate;
try (MarketDataClient client = new MarketDataClient()) {
var candles = client.stocks().candles(
StockCandlesRequest.builder(StockResolution.DAILY, "AAPL")
.from(LocalDate.parse("2024-01-01"))
.to(LocalDate.parse("2024-01-31"))
.build());
candles.values().forEach(System.out::println);
}
```
### Kotlin
```kotlin title="StockCandles.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.stocks.StockCandlesRequest
import com.marketdata.sdk.stocks.StockResolution
import java.time.LocalDate
MarketDataClient().use { client ->
val candles = client.stocks().candles(
StockCandlesRequest.builder(StockResolution.DAILY, "AAPL")
.from(LocalDate.parse("2024-01-01"))
.to(LocalDate.parse("2024-01-31"))
.build())
candles.values().forEach(::println)
}
```
### C#
```csharp title="StockCandles.cs"
using MarketDataApp;
using MarketDataApp.Stocks;
using var client = await MarketDataClient.CreateAsync();
var candles = await client.Stocks.GetCandlesAsync(
StockResolution.Daily, "AAPL",
from: DateOnly.Parse("2024-01-01"), to: DateOnly.Parse("2024-01-31"));
foreach (var candle in candles.Values)
{
Console.WriteLine(candle);
}
```
## Response Example
```json
{
"s": "ok",
"c": [217.68, 221.03, 219.89],
"h": [222.49, 221.5, 220.94],
"l": [217.19, 217.1402, 218.83],
"o": [221.03, 218.55, 220],
"t": [1569297600, 1569384000, 1569470400],
"v": [33463820, 24018876, 20730608]
}
```
## Request Parameters
### Required
- **resolution** `string`
The duration of each candle. *Case-insensitive*.
**Intraday Resolutions:**
- Minutely Resolutions: (`minutely`, `1`, `3`, `5`, `15`, `30`, `45`, ...)
- Hourly Resolutions: (`hourly`, `H`, `1H`, `2H`, ...)
**Daily Resolutions:**
- Daily Resolutions: (`daily`, `D`, `1D`, `2D`, ...)
- Weekly Resolutions: (`weekly`, `W`, `1W`, `2W`, ...)
- Monthly Resolutions: (`monthly`, `M`, `1M`, `2M`, ...)
- Yearly Resolutions: (`yearly`, `Y`, `1Y`, `2Y`, ...)
- **symbol** `string`
The company's ticker symbol.
### Dates
All `date` parameters are optional. By default the most recent candle is returned if no date parameters are provided.
- **from** `date`
The leftmost candle on a chart (inclusive). From and countback are mutually exclusive. If you use `countback`, `from` must be omitted. Accepted timestamp inputs: ISO 8601, unix, spreadsheet.
- **to** `date`
The rightmost candle on a chart (inclusive). Accepted timestamp inputs: ISO 8601, unix, spreadsheet.
- **countback** `number`
Will fetch a specific number of candles before (to the left of) `to`. From and countback are mutually exclusive. If you use `from`, `countback` must be omitted.
> [!NOTE]
> There is no maximum date range limit on daily candles. When requesting intraday candles of any resolution, no more than 1 year of data can be requested in a single request.
### Optional
- **extended** `boolean`
Include extended hours trading sessions when returning *intraday* candles. Daily resolutions _never_ return extended hours candles.
- Daily candles default: `false`.
- Intraday candles default: `false`.
- **adjustsplits** `boolean`
Adjust historical data for stock splits. Market Data uses the CRSP methodology for adjustment. Only certain combinations with `adjustdividends` are supported — see [Split and Dividend Adjustments](#split-and-dividend-adjustments).
- Daily candles default: `true`.
- Intraday candles default: `false`.
- **adjustdividends** `boolean`
Adjust historical data for dividends. Market Data uses the CRSP methodology for adjustment. Dividend adjustment is available for daily resolutions only. Unsupported combinations are rejected with an HTTP 400 — see [Split and Dividend Adjustments](#split-and-dividend-adjustments).
- Daily candles default: `true`.
- Intraday candles default: `false`.
- **adjustdividends** `boolean`
Adjust historical data for cash dividends. Market Data uses the CRSP methodology for adjustment.
- Daily candles default: `true`.
- Intraday candles default: `false`.
By default, daily candles are fully CRSP-adjusted for both splits and dividends. To retrieve split-adjusted-only data (the most common request), set `adjustdividends=false` and leave `adjustsplits` at its default of `true`.
> [!NOTE]
> There is no dividend-only adjustment. Requesting `adjustsplits=false` while `adjustdividends` remains `true` (its default) is unsupported and returns an HTTP `400` error. Because `adjustdividends` defaults to `true` on daily candles, passing only `adjustsplits=false` resolves to this rejected combination — the response's `errmsg` states exactly this and tells you to also set `adjustdividends=false` explicitly. To retrieve fully raw, unadjusted daily data, you must set both `adjustsplits=false` and `adjustdividends=false` explicitly.
### Split and Dividend Adjustments
Only three price series are supported: **raw**, **split-only**, and **fully adjusted** (CRSP-style). There is no dividend-only series — no data source publishes one — so that combination is rejected with an HTTP 400.
**Daily** — the `adjustsplits=false, adjustdividends=true` combination is rejected. Because `adjustdividends` defaults to `true` on daily candles, this is most commonly triggered by passing `adjustsplits=false` alone. To receive raw prices on daily candles, set **both** `adjustsplits=false` and `adjustdividends=false`.
| `adjustsplits` | `adjustdividends` | Series | Result |
|:--------------:|:-----------------:|----------------|--------------|
| `false` | `false` | raw | ✅ |
| `true` | `false` | split-only | ✅ |
| `true` | `true` | fully adjusted | ✅ (default) |
| `false` | `true` | dividend-only | ❌ HTTP 400 |
The rejected daily combination returns:
```json
{"s":"error","errmsg":"Unsupported parameter combination: adjustsplits=false with adjustdividends=true. Note: adjustdividends defaults to true on daily candles, so if your request only sets adjustsplits=false (without explicitly setting adjustdividends), dividend adjustment is still being applied via the default. Dividend-only adjustment is not supported. To receive unadjusted prices, set both adjustsplits=false and adjustdividends=false explicitly. To keep CRSP-style full adjustment, omit adjustsplits (or set it to true)."}
```
**Intraday** — any `adjustdividends=true` is rejected regardless of `adjustsplits`, because no intraday data source implements dividend adjustment. The intraday default is `false`, so the error only occurs on an explicit `adjustdividends=true`.
| `adjustsplits` | `adjustdividends` | Series | Result |
|:--------------:|:-----------------:|----------------|--------------|
| `false` | `false` | raw | ✅ (default) |
| `true` | `false` | split-only | ✅ |
| `false` | `true` | dividend-only | ❌ HTTP 400 |
| `true` | `true` | fully adjusted | ❌ HTTP 400 |
Both rejected intraday combinations return:
```json
{"s":"error","errmsg":"adjustdividends=true is not supported for intraday candles. Dividend adjustment is only available for daily resolutions."}
```
## Response Attributes
### Success
- **s** `string`
ll always be `ok` when there is data for the candles requested.
- **o** `array[number]`
Open price.
- **h** `array[number]`
High price.
- **l** `array[number]`
Low price.
- **c** `array[number]`
Close price.
- **v** `array[number]`
Volume.
- **t** `array[number]`
Candle time. For intraday candles, this is the candle's start time. For daily and longer resolutions, this is midnight US Eastern Time on the session date. All timestamps use US Eastern Time (America/New_York). See [Response Timezone](https://www.marketdata.app/docs/docs/api/dates-and-times#response-timezone) for details.
### No Data
- **s** `string`
Status will be `no_data` if no candles are found for the request.
- **nextTime** `number` optional
Unix time of the next quote if there is no data in the requested period, but there is data in a subsequent period.
### Error
- **s** `string`
Status will be `error` if the request produces an error response.
- **errmsg** `string`
An error message.
## Usage Information
### Data Availability
**Real-time data is not available for this endpoint under any plan or entitlement.** The type of candle data you receive depends on your user type and UTP entitlement. This may include 15-minute delayed candles or historical candles (1 day old), depending on the plan or access level.
| User Type | UTP Entitlement | Candle Type |
|------------------|-----------------|------------------------|
| Non-Professional | ✅ | 15-min delayed |
| Non-Professional | ❌ | Historical (1 day old) |
| Professional | Any | Historical (1 day old) |
| Unknown | Any | Historical (1 day old) |
> [!NOTE]
> **[What are entitlements?]**
>
> Entitlements are permissions granted by exchanges that allow access to their data. To get 15-minute delayed candles, non-professional users need to sign the [UTP agreement](https://www.marketdata.app/docs/account/entitlements). [Learn more about entitlements](https://www.marketdata.app/docs/account/entitlements).
### Pricing
The cost of using the candle API endpoint is 1 credit per 1000 candles.
| Data Type | Cost Basis | Credits Required per Unit |
|------------------------|------------------|---------------------------|
| 15-minute Delayed Data | Per 1000 candles | 1 credit |
| Historical Data | Per 1000 candles | 1 credit |
| Real-Time Data | Not available | N/A |
---
# Stock Earnings
Source: https://www.marketdata.app/docs/api/stocks/earnings/index.md
Get historical earnings per share data or a future earnings calendar for a stock.
## Endpoint
```
https://api.marketdata.app/v1/stocks/earnings/{symbol}/
```
### Method
```
GET
```
## Request Example
### HTTP
**GET** [https://api.marketdata.app/v1/stocks/earnings/AAPL/](https://api.marketdata.app/v1/stocks/earnings/AAPL/)
### JavaScript
```js title="app.js"
import { MarketDataClient } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const earnings = await client.stocks.earnings("AAPL");
for (const e of earnings) {
console.log(
`FY${e.fiscalYear} Q${e.fiscalQuarter}: ` +
`reported=${e.reportedEPS} estimated=${e.estimatedEPS}`
);
}
} catch (error) {
console.error(error);
}
```
### TypeScript
```typescript title="app.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { StockEarningsResponse } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const earnings: StockEarningsResponse = await client.stocks.earnings("AAPL");
for (const e of earnings) {
console.log(
`FY${e.fiscalYear} Q${e.fiscalQuarter}: ` +
`reported=${e.reportedEPS} estimated=${e.estimatedEPS}`
);
}
} catch (error) {
console.error(error);
}
```
### Python
```python title="app.py"
from marketdata import MarketDataClient
client = MarketDataClient()
earnings = client.stocks.earnings("AAPL")
print(earnings)
```
### Go
```go title="stockEarnings.go"
import (
"fmt"
api "github.com/MarketDataApp/sdk-go"
)
func ExampleStockEarningsRequest() {
earningsReports, err := StockEarnings().Symbol("AAPL").Get()
if err != nil {
fmt.Print(err)
return
}
for _, report := range earningsReports {
fmt.Println(report)
}
}
```
### PHP
```php title="stockEarnings.php"
use MarketDataApp\Client;
$client = new Client();
$earnings = $client->stocks->earnings(symbol: "AAPL");
// Display formatted earnings summary
echo $earnings;
```
### Java
```java title="StockEarnings.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.stocks.StockEarningsRequest;
try (MarketDataClient client = new MarketDataClient()) {
client.stocks().earnings(StockEarningsRequest.of("AAPL")).values().forEach(System.out::println);
}
```
### Kotlin
```kotlin title="StockEarnings.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.stocks.StockEarningsRequest
MarketDataClient().use { client ->
client.stocks().earnings(StockEarningsRequest.of("AAPL")).values().forEach(::println)
}
```
### C#
```csharp title="StockEarnings.cs"
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
foreach (var report in (await client.Stocks.GetEarningsAsync("AAPL")).Values)
{
Console.WriteLine(report);
}
```
## Response Example
```json
{
"s": "ok",
"symbol": ["AAPL"],
"fiscalYear": [2023],
"fiscalQuarter": [1],
"date": [1672462800],
"reportDate": [1675314000],
"reportTime": ["before market open"],
"currency": ["USD"],
"reportedEPS": [1.88],
"estimatedEPS": [1.94],
"surpriseEPS": [-0.06],
"surpriseEPSpct": [-3.0928],
"updated": [1701690000]
}
```
## Request Parameters
### Required
- **symbol** `string`
The company's ticker symbol.
### Optional
- **from** `date`
The earliest earnings report to include in the output. If you use countback, `from` is not required. Accepted timestamp inputs: ISO 8601, unix, spreadsheet.
- **to** `date`
The latest earnings report to include in the output. Accepted timestamp inputs: ISO 8601, unix, spreadsheet.
- **countback** `number`
Countback will fetch a specific number of earnings reports before `to`. If you use `from`, countback is not required.
- **date** `date`
Retrieve a specific earnings report by date. Accepted timestamp inputs: ISO 8601, unix, spreadsheet.
## Response Attributes
### Success
- **s** `string`
Will always be `ok` when there is data for the symbol requested.
- **symbol** `array[string]`
The symbol of the stock.
- **fiscalYear** `array[number]`
The fiscal year of the earnings report. This may not always align with the calendar year.
- **fiscalQuarter** `array[number]`
The fiscal quarter of the earnings report. This may not always align with the calendar quarter.
- **date** `array[date]`
The last calendar day that corresponds to this earnings report.
- **reportDate** `array[date]`
The date the earnings report was released or is projected to be released.
:::warning[Estimated Dates for Future Earnings]
For upcoming earnings events, the `reportDate` is an **estimate** based on historical reporting patterns and is not a confirmed date. These dates frequently change as companies update their reporting schedules. Only after a company has actually reported its earnings does the `reportDate` become a confirmed, historical data point. Do not make trading decisions based on estimated future earnings dates.
:::
- **reportTime** `array[string]`
The value will be either `before market open`, `after market close`, or `during market hours`.
- **currency** `array[string]`
The currency of the earnings report.
- **reportedEPS** `array[number]`
The earnings per share reported by the company. Earnings reported are typically non-GAAP unless the company does not report non-GAAP earnings.
:::tip
GAAP (Generally Accepted Accounting Principles) earnings per share (EPS) count all financial activities except for discontinued operations and major changes in accounting methods. Non-GAAP EPS, on the other hand, typically doesn't include losses or devaluation of assets, and often leaves out irregular expenses like significant restructuring costs, large tax or legal charges, especially for companies not in the financial sector.
:::
- **estimatedEPS** `array[number]`
The average consensus estimate by Wall Street analysts.
- **surpriseEPS** `array[number]`
The difference (in earnings per share) between the estimated earnings per share and the reported earnings per share.
- **surpriseEPSpct** `array[number]`
The difference in percentage terms between the estimated EPS and the reported EPS, expressed as a decimal. For example, if the estimated EPS is 1.00 and the reported EPS is 1.20, the surpriseEPSpct would be 0.20 (or 20%).
- **updated** `array[date]`
The date/time the earnings data for this ticker was last updated.
### No Data
- **s** `string`
Status will be `no_data` if no earnings data can be found for the symbol.
### Error
- **s** `string`
Status will be `error` if the request produces an error response.
- **errmsg** `string`
An error message.
## Usage Information
### Data Availability
This endpoint provides historical earnings data and future earnings calendar information. Earnings data is fundamental data that is updated after companies report their quarterly and annual results.
| User Type | Exchange Entitlement | Data Type |
|-----------|----------------------|------------------------|
| All Users | Not Required | Historical/Fundamental |
### Pricing
The cost of using the earnings API endpoint is 1 credit per API call.
| Data Type | Cost Basis | Credits Required per Unit |
|---------------|--------------|---------------------------|
| Earnings Data | Per API call | 1 credit |
---
# Stock News
Source: https://www.marketdata.app/docs/api/stocks/news/index.md
> [!WARNING]
> **[Beta Endpoint]**
>
> The News endpoint is still in beta and has not yet been optimized for performance. Use caution before adding this endpoint in a prodution environment.
Get news for a stock.
## Endpoint
```
https://api.marketdata.app/v1/stocks/news/{symbol}/
```
### Method
```
GET
```
## Request Example
### HTTP
**GET** [https://api.marketdata.app/v1/stocks/news/AAPL/](https://api.marketdata.app/v1/stocks/news/AAPL/)
### JavaScript
```js title="app.js"
import { MarketDataClient } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const articles = await client.stocks.news("AAPL");
for (const a of articles) {
console.log(`${a.source}: ${a.headline}`);
}
} catch (error) {
console.error(error);
}
```
### TypeScript
```typescript title="app.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { StockNews } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const articles: StockNews[] = await client.stocks.news("AAPL");
for (const a of articles) {
console.log(`${a.source}: ${a.headline}`);
}
} catch (error) {
console.error(error);
}
```
### Python
```python title="app.py"
from marketdata import MarketDataClient
client = MarketDataClient()
news = client.stocks.news("AAPL")
print(news)
```
### Go
```go title="stockNews.go"
import (
"fmt"
api "github.com/MarketDataApp/sdk-go"
)
func ExampleStockNewsRequest_get() {
news, err := StockNews().Symbol("AAPL").Get()
if err != nil {
fmt.Print(err)
return
}
for _, article := range news {
fmt.Println(article)
}
}
```
### PHP
```php title="stockNews.php"
use MarketDataApp\Client;
$client = new Client();
$news = $client->stocks->news(symbol: "AAPL");
// Display formatted news summary
echo $news;
```
### Java
```java title="StockNews.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.stocks.StockNewsRequest;
try (MarketDataClient client = new MarketDataClient()) {
client.stocks().news(StockNewsRequest.of("AAPL")).values().forEach(System.out::println);
}
```
### Kotlin
```kotlin title="StockNews.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.stocks.StockNewsRequest
MarketDataClient().use { client ->
client.stocks().news(StockNewsRequest.of("AAPL")).values().forEach(::println)
}
```
### C#
```csharp title="StockNews.cs"
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
foreach (var article in (await client.Stocks.GetNewsAsync("AAPL")).Values)
{
Console.WriteLine(article);
}
```
## Response Example
```json
{
"s":"ok",
"symbol": ["AAPL"],
"headline": ["Whoa, There! Let Apple Stock Take a Breather Before Jumping in Headfirst."],
"content": ["Apple is a rock-solid company, but this doesn't mean prudent investors need to buy AAPL stock at any price."],
"source": ["https://investorplace.com/2023/12/whoa-there-let-apple-stock-take-a-breather-before-jumping-in-headfirst/"],
"publicationDate": ["1703041200"],
"updated": 1703041200
}
```
## Request Parameters
### Required
- **symbol** `string`
The company's ticker symbol.
### Optional
- **from** `date`
The earliest news to include in the output. If you use countback, `from` is not required. Accepted timestamp inputs: ISO 8601, unix, spreadsheet.
- **to** `date`
The latest news to include in the output. Accepted timestamp inputs: ISO 8601, unix, spreadsheet.
- **countback** `number`
Countback will fetch a specific number of news before `to`. If you use `from`, countback is not required.
- **date** `date`
Retrieve news for a specific day. Accepted timestamp inputs: ISO 8601, unix, spreadsheet.
## Response Attributes
### Success
- **s** `string`
Will always be `ok` when there is data for the symbol requested.
- **symbol** `array[string]`
The symbol of the stock.
- **headline** `array[string]`
The headline of the news article.
- **content** `array[string]`
The content of the article, if available.
:::tip
Please be aware that this may or may not include the full content of the news article. Additionally, it may include captions of images, copyright notices, syndication information, and other elements that may not be suitable for reproduction without additional filtering.
:::
- **source** `array[url]`
The source URL where the news appeared.
- **publicationDate** `array[date]`
The date the news was published on the source website.
- **updated** `date`
The timestamp of the last update to the news data.
### No Data
- **s** `string`
Status will be `no_data` if no news can be found for the symbol.
### Error
- **s** `string`
Status will be `error` if the request produces an error response.
- **errmsg** `string`
An error message.
## Usage Information
### Data Availability
This endpoint provides news articles related to stocks. News data is auxiliary data that is updated as articles are published and aggregated.
| User Type | Exchange Entitlement | Data Type |
|-----------|----------------------|----------------|
| All Users | Not Required | News/Auxiliary |
### Pricing
The cost of using the news API endpoint is 1 credit per API call.
| Data Type | Cost Basis | Credits Required per Unit |
|-----------|--------------|---------------------------|
| News Data | Per API call | 1 credit |
---
# Real-Time Prices
Source: https://www.marketdata.app/docs/api/stocks/prices/index.md
Get real-time midpoint prices for one or more stocks. This endpoint returns real-time prices for stocks, using the [SmartMid](https://www.marketdata.app/smart-mid/) model.
## Endpoint
```
https://api.marketdata.app/v1/stocks/prices/{symbol}/
```
or
```
https://api.marketdata.app/v1/stocks/prices/?symbols={symbol1},{symbol2},...
```
### Method
```
GET
```
## Request Examples
### Single Symbol
### HTTP
**GET** [https://api.marketdata.app/v1/stocks/prices/AAPL/](https://api.marketdata.app/v1/stocks/prices/AAPL/)
### JavaScript
```js title="app.js"
import { MarketDataClient } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const prices = await client.stocks.prices("AAPL");
console.log(prices[0].mid);
} catch (error) {
console.error(error);
}
```
### TypeScript
```typescript title="app.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { StockPrice } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const prices: StockPrice[] = await client.stocks.prices("AAPL");
console.log(prices[0].mid);
} catch (error) {
console.error(error);
}
```
### Python
```python title="app.py"
from marketdata import MarketDataClient
client = MarketDataClient()
prices = client.stocks.prices("AAPL")
print(prices)
```
### PHP
```php title="stockPrices.php"
use MarketDataApp\Client;
$client = new Client();
$prices = $client->stocks->prices("AAPL");
// Display formatted price
echo $prices;
```
### Java
```java title="StockPrices.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.stocks.StockPricesRequest;
try (MarketDataClient client = new MarketDataClient()) {
client.stocks().prices(StockPricesRequest.of("AAPL")).values().forEach(System.out::println);
}
```
### Kotlin
```kotlin title="StockPrices.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.stocks.StockPricesRequest
MarketDataClient().use { client ->
client.stocks().prices(StockPricesRequest.of("AAPL")).values().forEach(::println)
}
```
### C#
```csharp title="StockPrices.cs"
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
foreach (var price in (await client.Stocks.GetPriceAsync("AAPL")).Values)
{
Console.WriteLine(price);
}
```
### Multiple Symbols
### HTTP
**GET** [https://api.marketdata.app/v1/stocks/prices/?symbols=AAPL,META,MSFT](https://api.marketdata.app/v1/stocks/prices/?symbols=AAPL,META,MSFT)
### JavaScript
```js title="app.js"
import { MarketDataClient } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const prices = await client.stocks.prices(["AAPL", "META", "MSFT"]);
for (const p of prices) {
console.log(`${p.symbol}: mid=${p.mid}`);
}
} catch (error) {
console.error(error);
}
```
### TypeScript
```typescript title="app.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { StockPrice } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const prices: StockPrice[] = await client.stocks.prices(["AAPL", "META", "MSFT"]);
for (const p of prices) {
console.log(`${p.symbol}: mid=${p.mid}`);
}
} catch (error) {
console.error(error);
}
```
### Python
```python title="app.py"
from marketdata import MarketDataClient
client = MarketDataClient()
prices = client.stocks.prices(["AAPL", "META", "MSFT"])
print(prices)
```
### PHP
```php title="stockPrices.php"
use MarketDataApp\Client;
$client = new Client();
$prices = $client->stocks->prices(["AAPL", "META", "MSFT"]);
// Display formatted prices summary
echo $prices;
```
### Java
```java title="StockPrices.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.stocks.StockPricesRequest;
try (MarketDataClient client = new MarketDataClient()) {
client.stocks().prices(StockPricesRequest.of("AAPL", "META", "MSFT")).values().forEach(System.out::println);
}
```
### Kotlin
```kotlin title="StockPrices.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.stocks.StockPricesRequest
MarketDataClient().use { client ->
client.stocks().prices(StockPricesRequest.of("AAPL", "META", "MSFT")).values().forEach(::println)
}
```
### C#
```csharp title="StockPrices.cs"
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
foreach (var price in (await client.Stocks.GetPricesAsync(["AAPL", "META", "MSFT"])).Values)
{
Console.WriteLine(price);
}
```
## Response Example
```json
{
"s": "ok",
"symbol": ["AAPL", "META", "MSFT"],
"mid": [149.07, 320.45, 380.12],
"change": [-2.052, 1.23, -0.85],
"changepct": [-0.0088, 0.0039, -0.0022],
"updated": [1663958092, 1663958092, 1663958092]
}
```
## Request Parameters
### Required
You can provide the symbol(s) in one of two ways:
1. As part of the URL path:
- **symbol** `string`
The company's ticker symbol.
2. As a query parameter:
- **symbols** `string`
Comma-separated list of ticker symbols.
> [!TIP]
> **[Batching multi-symbol requests]**
>
> There is no application-level cap on the number of symbols, but the HTTP request line is limited to 4094 bytes. For typical 4-character tickers that's roughly 700–800 symbols per request. To stay clear of the limit, chunk into batches of **500 symbols or fewer**. See [400: Bad Request — URL Too Long](https://www.marketdata.app/docs/api/troubleshooting/bad-request#case-5-url-too-long-multi-symbol-requests) for details.
### Optional
- **extended** `boolean`
Control the inclusion of extended hours data in the price output. Defaults to `true` if omitted.
- When set to `true`, the most recent price is always returned, without regard to whether the market is open for primary trading or extended hours trading.
- When set to `false`, only prices from the primary trading session are returned. When the market is closed or in extended hours, a historical price from the last closing bell of the primary trading session is returned instead of an extended hours price.
## Response Attributes
### Success
- **s** `string`
Will always be `ok` when there is data for the symbols requested.
- **symbol** `array[string]`
Array of ticker symbols that were requested.
- **mid** `array[number]`
Array of midpoint prices, as calculated by the [SmartMid](https://www.marketdata.app/smart-mid/) model.
- **change** `array[number]`
Array of price changes in currency units compared to the closing price of the previous primary trading session.
- **changepct** `array[number]`
Array of price changes in percent, expressed as a decimal, compared to the closing price of the previous day. For example, a 3% change will be represented as 0.03.
> [!NOTE]
> - When the market is open for primary trading, **change** and **changepct** are always calculated using the current midpoint price and the last primary session close. When the market is closed or in extended hours, this criteria is also used as long as `extended` is omitted or set to `true`.
> - When `extended` is set to `false`, and the market is closed or in extended hours, prices from extended hours are not considered. The values for **change** and **changepct** will be calculated using the last two closing prices instead.
- **updated** `array[date]`
Array of date/times for each stock price. All timestamps use US Eastern Time (America/New_York). See [Response Timezone](https://www.marketdata.app/docs/docs/api/dates-and-times#response-timezone) for details.
### No Data
- **s** `string`
Status will only be `no_data` if no prices can be found for all of the symbols. If a price for any symbol can be returned, the request will be successful.
### Error
- **s** `string`
Status will be `error` if the request produces an error response.
- **errmsg** `string`
An error message.
## Usage Information
### Data Availability
This endpoint is available to all users and does not require any exchange entitlements. All users receive real-time prices regardless of their plan or access level. **Cached, delayed, and historical data are not available on this endpoint - only real-time prices are provided.**
| User Type | Exchange Entitlement | Price Type |
|-----------|----------------------|------------|
| All Users | Not Required | Real-time |
### Pricing
The cost of using the stock prices API endpoint is 1 credit per symbol.
| Data Type | Cost Basis | Credits Required per Unit |
|-----------------|---------------|---------------------------|
| Real-Time Data | Per symbol | 1 credit |
| Cached Data | Not available | N/A |
| Delayed Data | Not available | N/A |
| Historical Data | Not available | N/A |
---
# Delayed Stock Quotes
Source: https://www.marketdata.app/docs/api/stocks/quotes/index.md
Retrieve the most recent available delayed quote for one or more stocks.
## Endpoint
```
https://api.marketdata.app/v1/stocks/quotes/{symbol}/
```
or
```
https://api.marketdata.app/v1/stocks/quotes/?symbols={symbol1},{symbol2},...
```
### Method
```
GET
```
## Request Examples
### Single Symbol
### HTTP
**GET** [https://api.marketdata.app/v1/stocks/quotes/AAPL/](https://api.marketdata.app/v1/stocks/quotes/AAPL/)
### JavaScript
```js title="app.js"
import { MarketDataClient } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const quotes = await client.stocks.quotes("AAPL");
console.log(quotes);
} catch (error) {
console.error(error);
}
```
### TypeScript
```typescript title="app.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { StockQuote } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const quotes: StockQuote[] = await client.stocks.quotes("AAPL");
console.log(quotes);
} catch (error) {
console.error(error);
}
```
### Python
```python title="app.py"
from marketdata import MarketDataClient
client = MarketDataClient()
quotes = client.stocks.quotes("AAPL")
print(quotes)
```
### Go
```go title="stockQuote.go"
import (
"fmt"
api "github.com/MarketDataApp/sdk-go"
)
func ExampleStockQuoteRequest() {
quotes, err := StockQuote().Symbol("AAPL").Get()
if err != nil {
fmt.Print(err)
return
}
for _, quote := range quotes {
fmt.Println(quote)
}
}
```
### PHP
```php title="stockQuote.php"
use MarketDataApp\Client;
$client = new Client();
$quote = $client->stocks->quote("AAPL");
// Display formatted quote with price, change, bid/ask, volume
echo $quote;
```
### Java
```java title="StockQuote.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.stocks.StockQuoteRequest;
try (MarketDataClient client = new MarketDataClient()) {
client.stocks().quote(StockQuoteRequest.of("AAPL")).values().forEach(System.out::println);
}
```
### Kotlin
```kotlin title="StockQuote.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.stocks.StockQuoteRequest
MarketDataClient().use { client ->
client.stocks().quote(StockQuoteRequest.of("AAPL")).values().forEach(::println)
}
```
### C#
```csharp title="StockQuote.cs"
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
foreach (var quote in (await client.Stocks.GetQuoteAsync("AAPL")).Values)
{
Console.WriteLine(quote);
}
```
### Multiple Symbols
### HTTP
**GET** [https://api.marketdata.app/v1/stocks/quotes/?symbols=AAPL,META,MSFT](https://api.marketdata.app/v1/stocks/quotes/?symbols=AAPL,META,MSFT)
### JavaScript
```js title="app.js"
import { MarketDataClient } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const quotes = await client.stocks.quotes(["AAPL", "META", "MSFT"]);
for (const q of quotes) {
console.log(`${q.symbol}: bid=${q.bid}, ask=${q.ask}, last=${q.last}`);
}
} catch (error) {
console.error(error);
}
```
### TypeScript
```typescript title="app.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { StockQuote } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const quotes: StockQuote[] = await client.stocks.quotes(["AAPL", "META", "MSFT"]);
for (const q of quotes) {
console.log(`${q.symbol}: bid=${q.bid}, ask=${q.ask}, last=${q.last}`);
}
} catch (error) {
console.error(error);
}
```
### Python
```python title="app.py"
from marketdata import MarketDataClient
client = MarketDataClient()
quotes = client.stocks.quotes(["AAPL", "META", "MSFT"])
print(quotes)
```
### Go
```go title="stockQuotes.go"
import (
"fmt"
api "github.com/MarketDataApp/sdk-go"
)
func ExampleBulkStockQuotesRequest() {
quotes, err := api.BulkStockQuotes().Symbols([]string{"AAPL", "META", "MSFT"}).Get()
if err != nil {
fmt.Print(err)
return
}
for _, quote := range quotes {
fmt.Println(quote)
}
}
```
### PHP
```php title="stockQuotes.php"
use MarketDataApp\Client;
$client = new Client();
$quotes = $client->stocks->quotes(["AAPL", "META", "MSFT"]);
// Display formatted quotes summary
echo $quotes;
```
### Java
```java title="StockQuotes.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.stocks.StockQuotesRequest;
try (MarketDataClient client = new MarketDataClient()) {
client.stocks().quotes(StockQuotesRequest.of("AAPL", "META", "MSFT")).values().forEach(System.out::println);
}
```
### Kotlin
```kotlin title="StockQuotes.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.stocks.StockQuotesRequest
MarketDataClient().use { client ->
client.stocks().quotes(StockQuotesRequest.of("AAPL", "META", "MSFT")).values().forEach(::println)
}
```
### C#
```csharp title="StockQuotes.cs"
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
foreach (var quote in (await client.Stocks.GetQuotesAsync(["AAPL", "META", "MSFT"])).Values)
{
Console.WriteLine(quote);
}
```
## Response Example
```json
{
"s": "ok",
"symbol": ["AAPL", "META", "MSFT"],
"ask": [247.94, 641.42, 465.95],
"askSize": [200, 100, 200],
"bid": [247.87, 641.25, 465.94],
"bidSize": [400, 100, 40],
"mid": [247.905, 641.335, 465.945],
"last": [247.94, 641.42, 465.94],
"change": [-0.41, 7.84, 14.80],
"changepct": [-0.0017, 0.0124, 0.0328],
"volume": [27255134, 8921456, 26896268],
"updated": [1769210435, 1769210435, 1769210429]
}
```
## Request Parameters
### Required
You can provide the symbol(s) in one of two ways:
1. As part of the URL path:
- **symbol** `string`
The company's ticker symbol.
2. As a query parameter:
- **symbols** `string`
Comma-separated list of ticker symbols.
### Optional
- **52week** `boolean`
Enable the output of 52-week high and 52-week low data in the quote output. By default this parameter is `false` if omitted.
- **extended** `boolean`
Control the inclusion of extended hours data in the quote output. Defaults to `true` if omitted.
- When set to `true`, the most recent quote is always returned, without regard to whether the market is open for primary trading or extended hours trading.
- When set to `false`, only quotes from the primary trading session are returned. When the market is closed or in extended hours, a historical quote from the last closing bell of the primary trading session is returned instead of an extended hours quote.
## Response Attributes
### Success
- **s** `string`
Will always be `ok` when there is data for the symbol requested.
- **symbol** `array[string]`
Array of ticker symbols that were requested.
- **ask** `array[number]`
The ask price of the stock.
- **askSize** `array[number]`
The number of shares offered at the ask price.
- **bid** `array[number]`
The bid price.
- **bidSize** `array[number]`
The number of shares that may be sold at the bid price.
- **mid** `array[number]`
The midpoint price between the ask and the bid.
- **last** `array[number]`
The last price the stock traded at.
- **change** `array[number]`
The difference in price in currency units compared to the closing price of the previous primary trading session.
- **changepct** `array[number]`
The difference in price in percent, expressed as a decimal, compared to the closing price of the previous day. For example, a 3% change will be represented as 0.03.
> [!NOTE]
> - When the market is open for primary trading, **change** and **changepct** are always calculated using the last traded price and the last primary session close. When the market is closed or in extended hours, this criteria is also used as long as `extended` is omitted or set to `true`.
> - When `extended` is set to `false`, and the market is closed or in extended hours, quotes from extended hours are not considered. The values for **change** and **changepct** will be calculated using the last two closing prices instead.
- **52weekHigh** `array[number]`
The 52-week high for the stock. This parameter is omitted unless the optional 52week request parameter is set to true.
- **52weekLow** `array[number]`
The 52-week low for the stock. This parameter is omitted unless the optional 52week request parameter is set to true.
- **volume** `array[number]`
The number of shares traded during the current session.
- **updated** `array[date]`
The date/time of the current stock quote. All timestamps use US Eastern Time (America/New_York). See [Response Timezone](https://www.marketdata.app/docs/docs/api/dates-and-times#response-timezone) for details.
### No Data
- **s** `string`
Status will only be `no_data` if no quotes can be found for all of the symbols. If a quote for any symbol can be returned, the request will be successful.
### Error
- **s** `string`
Status will be `error` if the request produces an error response.
- **errmsg** `string`
An error message.
## Usage Information
### Data Availability
The type of quote you receive depends on your user type and UTP entitlement. This may include a 15-minute delayed quote or historical (1 day old) quote, depending on the plan or access level. Real-time quotes are not currently available.
| User Type | UTP Entitlement | Quote Type |
|------------------|-----------------|------------------------|
| Non-Professional | ✅ | 15-min delayed |
| Non-Professional | ❌ | Historical (1 day old) |
| Professional | Any | Historical (1 day old) |
| Unknown | Any | Historical (1 day old) |
> [!NOTE]
> **[What are entitlements?]**
>
> Entitlements are permissions granted by exchanges that allow access to their data. To get delayed quotes, users need to sign the [UTP agreement](https://www.marketdata.app/docs/account/entitlements). [Learn more about entitlements](https://www.marketdata.app/docs/account/entitlements).
### Pricing
The cost of using the stock quote API endpoint depends on the type of data you choose and your usage pattern. Here's a breakdown of the pricing:
| Data Type | Cost Basis | Credits Required per Unit |
|------------------|------------|---------------------------|
| 15m Delayed Data | Per symbol | 1 credit |
| Historical Data | Per symbol | 1 credit |
---
# Troubleshooting
Source: https://www.marketdata.app/docs/api/troubleshooting/index.md
The Market Data API uses standard HTTP status codes to respond to each request. By preparing your application to utilize these status codes, you can often times solve common errors, or retry failed requests.
## Successful Requests (2xx)
These are requests that are answered successfully.
> [!CAUTION]
> Some libraries are not prepared to handle HTTP 203 response codes as successful requests. Ensure the library you are using can accept a 203 response code the same way as a 200 response code.
- `200 OK` - Successfully answered the request.
- `203 NON-AUTHORITATIVE INFORMATION` - Successfully served the request from our caching server. Treat this result the same as STATUS 200.
- `204 NO CONTENT` - Indicates a successful request for explicitly requested cached data, but our cache server lacks cached data for the symbol requested. Resend the request using the live mode.
## Client Errors (4xx)
Client errors occur when Market Data cannot respond to a request due to a problem with the request. The request will need to be modified in order to get a different response.
> [!TIP]
> If you believe your request is correct and you received a 4xx reply in error, please ensure you log our complete response to your request, including the full response headers along with the complete JSON error message we deliver in our reply. Open a ticket at our help desk and provide this information to our support staff and we will investigate further.
- `400 BAD REQUEST` - Input could not be parsed or validated (for example URL construction issues, invalid parameter values/types, invalid date usage, or invalid option symbols). See [400: Bad Request](https://www.marketdata.app/docs/api/troubleshooting/bad-request) for the complete troubleshooting guide and [URL Anatomy (Path vs Query Parameters)](https://www.marketdata.app/docs/api/troubleshooting/url-parameters) for URL construction basics.
- `401 UNAUTHORIZED` - The token supplied with the request is missing, invalid, or cannot be used. See [Authentication](https://www.marketdata.app/docs/api/troubleshooting/authentication).
- `402 PAYMENT REQUIRED` - The requested action cannot be performed with your current plan, such as requesting data older than your plan allows, using premium endpoints on Free/Trial, or using `mode=cached` on Free/Trial. See [402: Payment Required](https://www.marketdata.app/docs/api/troubleshooting/payment-required).
- `403 FORBIDDEN` - Access denied. Only one device is permitted to access the API at a time. Your IP address has changed, and your account is temporarily blocked for security reasons. Please wait 5 minutes before trying again. See [Multiple IP Addresses](https://www.marketdata.app/docs/api/troubleshooting/multiple-ip-addresses).
- `404 NOT FOUND` - No data exists for the requested symbol or time period. Consider trying a different symbol or time frame.
- `413 PAYLOAD TOO LARGE` - The request payload is too large. This is often due to requesting more than 1 year of **intraday** candle data in a single request. Reduce the intraday time window (daily candles can span longer ranges). See [413: Payload Too Large](https://www.marketdata.app/docs/api/troubleshooting/payload-too-large).
- `429 TOO MANY REQUESTS` - Credit limit exceeded for your current plan window (daily for Free/Starter/Trader, per-minute for Quant/Prime). See [429: Credit Limit Reached](https://www.marketdata.app/docs/api/troubleshooting/running-out-of-credits).
- `429 TOO MANY REQUESTS` - Concurrent request limit reached (more than 50 simultaneous in-flight requests). See [429: Too Many Concurrent Requests](https://www.marketdata.app/docs/api/troubleshooting/too-many-concurrent-requests).
## Server Errors (5xx)
Server errors are used to indicate problems with Market Data's service. They are requests that appear to be properly formed, but can't be responded to due to some kind of problem with our servers.
### Permanent Failures
- `500 INTERNAL SERVER ERROR` - An unknown server issue prevents Market Data from responding to your request. Open a ticket with the helpdesk and include the Ray ID of the request. See [Service Outages](https://www.marketdata.app/docs/api/troubleshooting/service-outages).
### Temporary Failures
Most 5xx errors are temporary and resolve themselves on their own. Please retry requests that receive 5xx errors at a later time and they will probably be successful.
- `502 BAD GATEWAY` - Market Data's API server does not respond to the gateway, indicating the API is offline or unreachable. This error is normally sporadic and due to network issues and will typically resolve itself within 1-2 minutes. See [Service Outages](https://www.marketdata.app/docs/api/troubleshooting/service-outages).
- `503 SERVICE UNAVAILABLE` - Market Data's API server is accessible but cannot fulfill the request, usually due to server overload. Retry in a few minutes. See [Service Outages](https://www.marketdata.app/docs/api/troubleshooting/service-outages).
- `504 GATEWAY TIMEOUT` - Market Data's load balancer received no response from the API, suggesting the request is taking too long to resolve. Retry in 1-2 minutes and report to the helpdesk if the issue persists. See [Service Outages](https://www.marketdata.app/docs/api/troubleshooting/service-outages).
- `509 API ENDPOINT OVERLOADED` - The endpoint is currently overloaded. Retry in a few minutes and report to the helpdesk if the issue continues for more than 15 minutes. See [Service Outages](https://www.marketdata.app/docs/api/troubleshooting/service-outages).
- `524 A TIMEOUT OCCURRED` - The Market Data API failed to provide an HTTP response before the default 100-second connection timeout, possibly due to server overload or resource struggles. Contact support if this continues for more than 15 minutes. See [Service Outages](https://www.marketdata.app/docs/api/troubleshooting/service-outages).
- `529 DATABASE OFFLINE` - The database is offline, overloaded, or not responding. Contact support or submit a ticket if this error persists for more than 15 minutes. See [Service Outages](https://www.marketdata.app/docs/api/troubleshooting/service-outages).
- `530 DATABASE ERROR` - The request resulted in a database error. Contact support or submit a ticket with your API request details. See [Service Outages](https://www.marketdata.app/docs/api/troubleshooting/service-outages).
- `540 API ENDPOINT OFFLINE` - The Market Data API endpoint expected to respond is offline. Report to the helpdesk if this persists for more than 15 minutes. See [Service Outages](https://www.marketdata.app/docs/api/troubleshooting/service-outages).
- `598 API GATEWAY OFFLINE` - The gateway server is not responding or is unavailable. Report to the helpdesk if this issue continues for more than 15 minutes. See [Service Outages](https://www.marketdata.app/docs/api/troubleshooting/service-outages).
---
# 401: Authentication
Source: https://www.marketdata.app/docs/api/troubleshooting/authentication/index.md
Authentication issues usually arise due to incorrect headers, omission of the authorization header, or problems with URL parameters. If you encounter a 401 error, it's usually related to issues with your `Authorization` header. The most common issues are:
- Incorrect token
- Invalid characters to separate the query string from the path
- Invalid characters to separate query parameters from each other
Troubleshooting authentication issues is crucial for ensuring uninterrupted access to our API services. Most authentication issues are related to header-based authentication, but URL parameter authentication can also be troublesome to users who are getting started with Market Data as their first REST API. This guide aims to provide you with steps to resolve common problems.
> [!TIP]
> Even though it is more complex to set-up, we encourage all users to take the extra time required to configure header-based authentication for our API, as this is the most secure method of authentication.
## Troubleshooting URL Parameter Authentication
Usually URL parameter authentication goes wrong because customers use **invalid characters to separate the query string from the path**. The correct character to use is `?` and the correct character to use to separate query parameters is `&`. If you use the wrong characters, the API will not be able to parse the query string correctly and will be unable to authenticate your request. Learn more about the correct format of URL parameters [here](https://www.marketdata.app/docs/api/troubleshooting/url-parameters).
For example, suppose your token was `token1234` and you were also using the `dateformat` parameter to request a timestamp as the output format for the time. For a stocks/quotes request, the correct URL would be:
```http
https://api.marketdata.app/v1/stocks/quotes/SPY/?token=token1234&dateformat=timestamp
```
- Note how the token is separated from the path by a `?` and the dateformat parameter is separated from the token by a `&`.
The ordering of the parameters is not important. You do not need to put `token` as the first parameter. It would also be perfectly valid to use the following URL:
```http
https://api.marketdata.app/v1/stocks/quotes/SPY/?dateformat=timestamp&token=token1234
```
No matter the order of the parameters, the API will be able to parse the query string and authenticate your request **as long as the correct characters are used to separate the query string from the path and the query parameters from each other.**
## Troubleshooting Header Authentication
The most common issues customers face with header-based authentication are:
- Omission of the header
- Incorrect header name
- Invalid header format
- Incorrect token
### Steps for Troubleshooting 401 Errors
1. **Test the Token with URL Parameter Authentication**
```bash
curl -X GET "https://api.marketdata.app/v1/stocks/quotes/SPY/?token=YOUR_TOKEN"
```
Use CURL to test your token using URL parameter authentication. If it works, you know that your token is valid. If you are using a token that is not valid, you will receive a 401 error. If you are using a token that is valid, you will receive a `200 OK` response along with a stock quote.
2. **Inspect Request Headers**
To inspect the headers your application is sending, especially the `Authorization` header, use our dedicated [headers endpoint](https://www.marketdata.app/docs/api/utilities/headers). This will help you identify any discrepancies in the headers that might be causing authentication issues.
```bash
curl -X GET "https://api.marketdata.app/headers/" -H "Authorization: Bearer YOUR_TOKEN"
```
Make a request to https://api.marketdata.app/headers/ from your application and save the headers. This endpoint will return a JSON response of the headers your application is sending, with sensitive headers like `Authorization` partially redacted for security. Compare the headers from your application's request to the expected headers.
If your application's `Authorization` header is different from what you expect, there may be an issue with how your application is setting headers. If the headers match your expectations, the issue may lie elsewhere, possibly with the token itself.
3. **Log Response Headers and Submit a Helpdesk Ticket**
```bash
curl -X GET "https://api.marketdata.app/v1/stocks/quotes/SPY/" -H "Authorization: Bearer YOUR_TOKEN" -i
```
Finally, we'll now make a header-authentication request using your token. Make a request using the CURL command above. If you receive a 401 error, the issue is with your token. If you receive a `200 OK` response, the issue is with your application's code.
> [!TIP]
> If the issue persists, include the log data and the Ray ID (listed in the CF-Ray header) when you submit a helpdesk ticket. This will help our support team locate your specific request in our server logs and assist you more effectively.
## Opening a Support Ticket
### When to Open a Ticket
Open a ticket if you experience persistent issues or errors that cannot be resolved through this troubleshooting guide.
### What to Include
Include the log data and the Ray ID for faster resolution. By attaching this information to your support ticket, it becomes much easier for our staff to understand and solve your ticket.
---
# 400: Bad Request
Source: https://www.marketdata.app/docs/api/troubleshooting/bad-request/index.md
Use this page for **all `400 BAD REQUEST` errors**.
A `400` means the request could not be parsed or validated as valid input.
## What Usually Causes 400
1. URL is constructed incorrectly (path vs query string mixups)
- For path/query parsing issues caused by bad URL construction, see [400: URL Anatomy (Path vs Query Parameters)](https://www.marketdata.app/docs/api/troubleshooting/url-parameters).
2. Query parameters are malformed (`?` / `&` / `key=value` issues)
3. Parameter type or format is invalid (string where number/date is expected)
4. Parameter value is unsupported or out of range
5. Option symbol is not valid OCC format
6. `date` parameter is used in a current-data request (historical-only usage)
7. URL is too long — most often caused by packing too many tickers into a single multi-symbol request
## Case 1: URL Construction Problems (Path vs Query)
Common examples:
- Putting query parameters in the path segment
- Missing `?` before query parameters
- Using wrong separators between parameters
Correct structure:
```text
https://api.marketdata.app//?key=value&key2=value2
```
If you need a step-by-step URL-building guide, use:
- [400: URL Anatomy (Path vs Query Parameters)](https://www.marketdata.app/docs/api/troubleshooting/url-parameters)
## Case 2: Invalid Parameter Type/Format/Value
You can get `400` when a parameter cannot be parsed correctly or violates endpoint rules.
Examples:
- Sending text where a numeric value is expected
- Sending a malformed date
- Sending an unsupported enum/value for a parameter
- Sending a value outside allowed bounds
Fast check:
1. Confirm parameter names exactly match the endpoint docs.
2. Confirm each parameter value type/format is valid.
3. Remove optional parameters and re-add one at a time.
## Case 3: Invalid Option Symbol (OCC)
You may receive:
```json
{
"s": "error",
"errmsg": "Invalid option symbol. Use valid OCC symbology for the option symbol."
}
```
Meaning: request shape is valid, but `optionSymbol` is malformed.
For detailed format rules, examples, and validation workflow, use:
- [400: Invalid Option Symbol](https://www.marketdata.app/docs/api/troubleshooting/invalid-option-symbol)
## Case 4: `date` Parameter Is Historical-Only
You may receive:
```json
{
"s": "error",
"errmsg": "Invalid date. The date parameter is used for historical queries only."
}
```
Meaning:
- Request can be otherwise valid
- `date` was used in a current-data context
How to fix:
1. For current data: omit `date`
2. For historical data: provide a past trading date
```http
# Current request (correct): no date parameter
GET https://api.marketdata.app/v1/options/quotes/OPTION_SYMBOL/
# Historical request (correct): includes date
GET https://api.marketdata.app/v1/options/quotes/OPTION_SYMBOL/?date=2025-01-15
```
## Case 5: URL Too Long (Multi-Symbol Requests)
You may receive an HTML-formatted `400 BAD REQUEST` response that does **not** follow the usual JSON error shape:
```html
Bad Request
Bad Request
Request Line is too large (XXXX > 4094)
```
This is returned by the web server **before** the request reaches the API, so it does not appear in your account's request log and does not consume credits.
The cause is exceeding the **4094-byte HTTP request-line limit**. The most common trigger is packing too many tickers into a single multi-symbol call, for example:
- `/v1/stocks/prices/?symbols=...`
- `/v1/stocks/bulkcandles/D/?symbols=...`
### How many symbols per request
There is no application-level cap on symbol count — the ceiling is set entirely by URL length. As a rule of thumb:
- An average 4-character ticker plus a separator comma takes 5 bytes.
- Path, `token`, `format`, `columns`, and other query parameters typically consume 150–250 bytes of overhead.
- That leaves roughly 3,800 bytes for the symbols list — about **700–800 typical-length tickers** before requests begin to fail.
To stay clear of the limit, **chunk multi-symbol requests into batches of 500 symbols or fewer.** That leaves headroom for longer tickers (BRK.B, 5-letter Nasdaq tickers) and any additional query parameters you may add later.
### How to fix
1. Reduce the number of symbols in the request.
2. Split your symbol list into multiple smaller calls.
3. If you must send a long list, drop any non-essential query parameters to free up URL space.
## 400 vs Other Status Codes
- `400`: request input/format is invalid
- `401`: authentication issue (token missing/invalid)
- `402`: plan/entitlement limit reached
- `403`: forbidden by policy (for example multiple IP addresses)
- `404`: request format is valid, but requested resource was not found
## If You Still Get 400
1. Log full request URL and all query parameters
2. Compare request against endpoint docs field-by-field
3. Re-test with only required parameters
4. Add optional parameters back one at a time
---
# 400: Invalid Option Symbol
Source: https://www.marketdata.app/docs/api/troubleshooting/invalid-option-symbol/index.md
Use this page when you receive a `400 BAD REQUEST` with:
```json
{
"s": "error",
"errmsg": "Invalid option symbol. Use valid OCC symbology for the option symbol."
}
```
If you need the broader `400` troubleshooting flow, start here:
- [400: Bad Request](https://www.marketdata.app/docs/api/troubleshooting/bad-request)
## What This Error Means
The endpoint and query string can be valid, but the `optionSymbol` itself is not valid OCC format.
## OCC Option Symbol Format
Expected format:
`UNDERLYING + YYMMDD + C/P + STRIKE`
Example:
- `AAPL271217C00250000`
Field rules:
1. `UNDERLYING`: Root symbol (for example `AAPL`, `SPY`, `INTC`)
2. `YYMMDD`: Expiration date (2-digit year, month, day)
3. `C/P`: `C` for call or `P` for put
4. `STRIKE`: 8 digits, zero-padded, implied 3 decimal places
## Strike Encoding Reference
The strike is represented as an integer with three implied decimal places.
Examples:
- `150` becomes `00150000`
- `32.5` becomes `00032500`
- `32.25` becomes `00032250`
## Common Construction Errors
1. Expiration is not in `YYMMDD`
2. Missing call/put flag (`C` or `P`)
3. Strike field is not 8 digits
4. Strike is not zero-padded
5. Decimal strike is encoded incorrectly
6. Legacy pre-2010 style symbol is used
## Validate Before Calling Quotes/History
Use the lookup endpoint to build/validate a symbol first.
```http
# Build/validate OCC symbol from human-readable contract
GET https://api.marketdata.app/v1/options/lookup/AAPL%201/17/2025%20150%20Call
# Then request data with the returned optionSymbol
GET https://api.marketdata.app/v1/options/quotes/AAPL271217C00250000/
```
## 400 vs 404 for Options
- `400`: the option symbol format is invalid
- `404`: format is valid, but no matching contract/data was found
## Quick Debug Checklist
1. Log the exact `optionSymbol` your client sent
2. Check `YYMMDD`, `C/P`, and 8-digit strike encoding
3. Validate with `options/lookup`
4. Reuse the exact returned symbol in follow-up requests
---
# Logging
Source: https://www.marketdata.app/docs/api/troubleshooting/logging/index.md
## Why Logging is Important
Logging our API's responses is crucial for monitoring the behavior of your application and troubleshooting problems, either with our API or your usage of the API.
### What Should Be Logged
Market Data responds to successful requests with either a 200 or 203 status code. Therefore, we recommend you log any response that doesn't have a 2xx status code. Successful responses may be logged if you wish, but normally this is not necessary.
When logging errors, the log should include the exact request made, Market Data's response, and the CF-Ray header.
## Logging Examples
### JavaScript
```js title="logger.js"
import { MarketDataClient, DefaultLogger, LogLevel } from "@marketdata/sdk";
// Enable DEBUG-level logging to capture full request/response diagnostics
// (including the CF-Ray header) for every API call.
const client = new MarketDataClient({
logger: new DefaultLogger(LogLevel.DEBUG),
});
try {
const quotes = await client.stocks.quotes("AAPL");
console.log(quotes);
} catch (error) {
// The SDK logs the failing request and response automatically;
// handle the error here as needed.
console.error(error);
}
```
### TypeScript
```typescript title="logger.ts"
import { MarketDataClient, DefaultLogger, LogLevel } from "@marketdata/sdk";
import type { StockQuote } from "@marketdata/sdk";
// Enable DEBUG-level logging to capture full request/response diagnostics
// (including the CF-Ray header) for every API call.
const client = new MarketDataClient({
logger: new DefaultLogger(LogLevel.DEBUG),
});
try {
const quotes: StockQuote[] = await client.stocks.quotes("AAPL");
console.log(quotes);
} catch (error) {
// The SDK logs the failing request and response automatically;
// handle the error here as needed.
console.error(error);
}
```
### Python
```python title="logger.py"
from marketdata import MarketDataClient, MarketDataClientErrorResult
import json
# Make the API request using the SDK
client = MarketDataClient()
result = client.stocks.quotes("AAPL")
# Check if the result is an error (SDK handles HTTP errors internally)
if isinstance(result, MarketDataClientErrorResult):
log_data = {
"request": f"GET https://api.marketdata.app/v1/stocks/quotes/AAPL/",
"response": str(result.error),
"cf_ray_header": "Not available" # CF-Ray is handled internally by the SDK
}
# Save to a logfile
with open("api_error_log.json", "a") as logfile:
logfile.write(json.dumps(log_data) + "\n")
```
### Java
```java title="Logger.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.exception.MarketDataException;
import com.marketdata.sdk.stocks.StockQuotesRequest;
// Enable DEBUG logging by setting MARKETDATA_LOGGING_LEVEL=DEBUG in your
// environment. The SDK then logs full request/response diagnostics (including
// the CF-Ray request id) on the "com.marketdata.sdk" java.util.logging logger.
try (MarketDataClient client = new MarketDataClient()) {
var quotes = client.stocks().quotes(StockQuotesRequest.of("AAPL"));
System.out.println(quotes.values());
} catch (MarketDataException e) {
// The SDK already logged the failing request/response. getSupportInfo()
// bundles the status code, request URL and request id for a bug report.
System.err.println(e.getSupportInfo());
}
```
### Kotlin
```kotlin title="Logger.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.exception.MarketDataException
import com.marketdata.sdk.stocks.StockQuotesRequest
// Enable DEBUG logging via the MARKETDATA_LOGGING_LEVEL=DEBUG environment
// variable; the SDK logs request/response diagnostics on "com.marketdata.sdk".
MarketDataClient().use { client ->
try {
val quotes = client.stocks().quotes(StockQuotesRequest.of("AAPL"))
println(quotes.values())
} catch (e: MarketDataException) {
System.err.println(e.supportInfo)
}
}
```
### C#
```csharp title="Logger.cs"
using MarketDataApp;
using MarketDataApp.Exceptions;
using Microsoft.Extensions.Logging;
// Enable DEBUG logging by setting MARKETDATA_LOGGING_LEVEL=DEBUG in your environment.
// The SDK then emits full request/response diagnostics (including the CF-Ray request
// id) as structured Microsoft.Extensions.Logging events through the logger you attach.
using var loggerFactory = LoggerFactory.Create(builder => builder.AddMarketDataCanonicalConsole());
var options = MarketDataClientOptions.FromEnvironment()
.WithLogger(loggerFactory.CreateLogger());
try
{
using var client = await MarketDataClient.CreateAsync(options);
var quotes = await client.Stocks.GetQuotesAsync(["AAPL"]);
Console.WriteLine(quotes);
}
catch (MarketDataException e)
{
// The SDK already logged the failing request/response. SupportInfo bundles the
// status code, request URL and request id for a bug report.
Console.Error.WriteLine(e.SupportInfo);
}
```
## The CF-Ray Header
### What is the CF-Ray Header
The CF-Ray header (otherwise known as a Ray ID) is a hashed value that encodes information about the Cloudflare data center and the request. Every request that travels through the Cloudflare network is assigned a unique Ray ID for tracking.
### Why It's Important
Since Market Data operates on the Cloudflare network, we log each of our API responses with Cloudflare's Ray ID. This allows us to have a unique identifier for each and every API request made to our systems. Additionally, we can also trace all requests through the Cloudflare network from our servers to your application.
> [!TIP]
> When opening a ticket at the customer helpdesk, if a Ray ID is provided to our support staff, we'll be able to identify the exact request you made and find why it produced an error.
## Opening a Support Ticket
### When to Open a Ticket
Open a ticket if you experience persistent issues or errors that cannot be resolved through logging. For example, if you are making properly formatted requests to our systems and you are getting INTERNAL SERVER ERROR messages.
### What to Include
Include the log data and the CF-Ray header value for faster resolution. By attaching your log data to your support ticket, it becomes much easier for our staff to understand and solve your ticket.
---
# 403: Multiple IP Addresses
Source: https://www.marketdata.app/docs/api/troubleshooting/multiple-ip-addresses/index.md
> [!NOTE]
> **[Automatic Resolution]**
>
> If your account is blocked, you do not need to contact support. The block will automatically resolve after 5 minutes if you stop making API requests. Simply wait 5 minutes without making any requests from the blocked IP address, then resume with a single device or IP address.
>
> During the block, you can continue making requests from the previously authorized IP address.
If you encounter an issue where your account is temporarily blocked due to accessing the API from multiple devices, follow these steps to resolve it. For background on why this restriction exists, see the [Single IP Address Policy](https://www.marketdata.app/docs/docs/account/data-policies/single-ip/).
## Problem
Your account is blocked because it was accessed from multiple IP addresses in a pattern that suggests simultaneous device usage. While **changing IP addresses is permitted**, rapidly switching back and forth between previously used IP addresses indicates multiple active devices, which is against our policy.
When this occurs, you'll receive an error response that includes detailed information to help you identify the source of the conflict:
```json
{
"s": "error",
"errmsg": "Access denied. Only one device is permitted. Your IP address has changed, and your account is temporarily blocked for security reasons. Please wait 5 minutes before trying again.",
"authorizedIP": "107.178.202.2",
"blockedIP": "44.116.21.32",
"troubleshootingGuide": "https://www.marketdata.app/docs/api/troubleshooting/multiple-ip-addresses"
}
```
The error response includes:
- The IP address of your previous request (`authorizedIP`)
- The IP address of your current request (`blockedIP`)
You can use these IP addresses to:
1. Identify which devices or services are making the conflicting requests.
2. Check if the IPs match known ISPs, VPNs, cloud services, or other servers you're using.
3. Compare with your network configuration to determine if load balancing, round-robin outgoing requests, or failover is causing the issue.
### How It Works
When you access the API:
- Each new IP address you connect from is recorded
- If you connect from IP address A, then switch to IP address B, that's permitted.
- However, if you then switch back to IP address A while IP address B is still active within 5 minutes, this suggests two devices are being used simultaneously.
- This pattern triggers an **automatic temporary block** on your account.
- During the block, you can continue making requests from your previously authorized IP address.
- After the block expires, you can switch to a different IP address again, but should avoid switching back to recently used IPs.
### Common Scenarios
1. **VPN Usage**: If you connect to a VPN, then disconnect, your IP address may change back and forth rapidly. This can happen if you frequently switch between a VPN and your regular network.
2. **Wi-Fi Network Changes**: Switching between different Wi-Fi networks in a short period can result in different IP addresses being used within a single five minute period.
3. **Load Balancing**: Servers making outgoing requests might use multiple IP addresses, especially if they are part of a load-balanced setup, round-robin configuration, or if they use multiple network interfaces.
4. **Proxy Usage**: Using proxies or rotating proxies can cause your requests to appear from different IP addresses even if you are using the same device.
5. **Cloud Functions**: Cloud services like Amazon Web Services, Google Cloud, or Azure may send requests from ephemeral instances that change IP addresses frequently.
6. **Google Apps Script**: Using UrlFetch in Google Apps Script can result in requests coming from different IP addresses simultaneously, as Google's infrastructure will rotate requests through various servers.
## Solution
1. **Wait for 5 Minutes**: The block is temporary. Wait for 5 minutes before attempting to access the API again. During this time, ensure all your devices stop their API access.
2. **Use a Single Active Device**: While you can change devices or IP addresses:
- Make sure to completely stop API access from your previous device before switching.
- Do not switch devices more than once every 5 minutes.
- Do not attempt to connect using multiple devices.
3. **For Cloud Systems**: If you're running cloud systems that make API calls:
- Use a single cloud instance to make API calls.
- Ensure old instances are fully terminated before starting new ones.
- Purchase a dedicated IP address for your cloud instance if it will be starting and stopping more than once every 5 minutes.
4. **Serverless Functions Are Not Supported (Lambda, Google Cloud Functions, Google Apps Script, etc.)**: Your subscription is licensed to run on a **single device**. Serverless platforms execute your code across a pool of ephemeral instances with rotating outbound IP addresses, which inherently bypasses the single-device licensing terms. For this reason, serverless functions are not compatible with the API and are not a supported deployment model. Run your workload on a single, persistent device or instance with a stable IP address instead.
5. **Multiple Environments (Development, Staging, Production)**: If you're running more than one environment that calls the API at the same time using the same token, you will hit this block. Sharing the token across environments is also a token-sharing violation under the [Single IP Address Policy](https://www.marketdata.app/docs/docs/account/data-policies/single-ip/).
Compliant options:
- **Serialize**: run only one environment at a time. For most testing and A/B workflows, running scenarios sequentially is sufficient.
- **Use the no-auth demo symbols in development**: stock and option endpoints accept **AAPL** (and any AAPL option contract, e.g. `AAPL271217C00250000`) with no token at all. Development and integration tests that exercise the API surface — request shape, response parsing, error handling — can use these symbols without ever touching your account. See [Authentication › Demo The API With No Authentication](https://www.marketdata.app/docs/docs/api/authentication/#demo-the-api-with-no-authentication). Note that no-auth requests return historical data only.
- **Commercial Plan**: if your use case genuinely requires simultaneous parallel data across multiple environments (not testing), the [Commercial Plan](https://www.marketdata.app/docs/docs/account/plans/commercial/) has no IP limit, so you can call the API from as many servers and IP addresses as your product needs.
## Important Note
The system is designed to detect patterns of simultaneous access, not just IP changes. While you are permitted to change devices, attempting to maintain multiple active connections from different computers — even when egressed through a single shared IP — will result in account blocks and may result in suspension.
For further assistance, please contact our support team.
---
# 404: Not Found
Source: https://www.marketdata.app/docs/api/troubleshooting/not-found/index.md
A `404` can mean different things depending on endpoint and request context.
This guide is intentionally detailed and will be built out case by case.
## Case 1: Market Closed on Requested Date
One of the most helpful `404` responses is:
```json
{
"s": "no_data",
"errmsg": "Market closed on this date."
}
```
What this means:
- Your symbol/request shape is valid.
- The requested date is not a trading day for that market/session.
- The API returns `no_data` to explain that the date context is invalid for quote/candle retrieval.
What to do:
1. Check if the requested date is a weekend or market holiday.
2. Retry with the nearest trading day.
3. If using automated date logic, add trading-calendar validation before requesting.
## Case 2: Valid OCC Symbol Format, But Contract Does Not Exist
Another common `404` response is:
```json
{
"s": "error",
"errmsg": "No option found. No option was found for this strike and expiration."
}
```
What this means:
- The option symbol format is valid OCC symbology.
- But no real option contract exists for that strike/expiration combination.
- This is not a malformed-parameter case (`400`) and not a date-only no-data case.
- The API treats this as a non-existent contract and returns `404`.
How this differs from other errors:
1. `400 invalid option symbol`: OCC format itself is malformed.
2. `404 no_data` market/date cases: symbol can exist, but no data for that date/session.
3. This case: OCC format is valid, but the contract likely never existed.
What to do:
1. Verify strike and expiration against available contracts for the underlying.
2. Use `options/chain` or `options/expirations` to confirm the contract exists.
3. Use `options/lookup` to avoid manually constructing symbols.
## Case 3: Valid Symbol, But Requested Time Has No Available Data
Another common `404`/`no_data` response is:
```json
{
"s": "no_data",
"nextTime": null,
"prevTime": null
}
```
What this means:
- The symbol/request can be valid.
- But there is no data available around the requested time in either direction.
- This can happen for both stocks and options.
Common reasons:
1. Requested date is outside available historical coverage for that symbol/endpoint.
2. Valid option contract was requested before it began quoting.
3. Requested window has no recorded quotes/candles and there is no adjacent data to return.
Important context:
- This is not necessarily customer error.
- It can represent a legitimate \"data does not exist in this time region\" condition.
What to do:
1. Move the request window forward in time and retry.
2. For options, confirm the contract had started trading by that date.
3. Use nearby date probes to find first/last available data for the symbol.
---
# 413: Payload Too Large
Source: https://www.marketdata.app/docs/api/troubleshooting/payload-too-large/index.md
A `413 PAYLOAD TOO LARGE` means your request asked for more candle data than is allowed in a single call.
For candles, this is most commonly triggered on **intraday resolutions** when you request **more than 1 year** in one request.
## Fast Fix
For intraday candles, request less than 1 year per API call.
## Why This Happens
The candles endpoints enforce request-size constraints to keep responses bounded and reliable. If your intraday candle range is too large, the API returns `413`.
Daily candles are allowed beyond 1 year.
## What To Change
1. For intraday requests, reduce the date window to less than 1 year.
2. If you need more history, split your query into multiple requests.
## Example
```http
# Too large (intraday example)
GET https://api.marketdata.app/v1/stocks/candles/1/AAPL/?from=2022-01-01&to=2024-12-31
# Valid intraday: keep each request under 1 year
GET https://api.marketdata.app/v1/stocks/candles/1/AAPL/?from=2024-01-01&to=2024-12-31
# Daily example: multi-year range is allowed
GET https://api.marketdata.app/v1/stocks/candles/D/AAPL/?from=2015-01-01&to=2024-12-31
```
## If You Still Get 413
1. Double-check `from`/`to` ranges after timezone normalization.
2. Verify your client is not expanding ranges unexpectedly.
3. Contact support with your full request URL and parameters.
---
# 402: Payment Required
Source: https://www.marketdata.app/docs/api/troubleshooting/payment-required/index.md
A `402 PAYMENT REQUIRED` means your request is valid, but your current plan does not include the specific data or feature you requested.
This is a **plan entitlement** issue, not an authentication issue and not a rate-limit issue.
## Fast Triage
You are likely hitting `402` when one of these is true:
1. You requested historical data older than your plan allows.
2. You requested a premium endpoint from Free/Trial.
3. You requested `mode=cached` from Free/Trial.
## What Triggers 402 by Plan
### Free Forever and Trial Plans
Common 402 causes:
1. Historical query older than 1 year.
2. Premium endpoint request.
3. `mode=cached` request.
### Starter Plan
Common 402 cause:
1. Historical query older than 5 years.
### Trader, Quant, Prime
Trader, Quant, and Prime should not receive `402 PAYMENT REQUIRED` for standard plan usage.
If you receive `402` on one of these plans, treat it as unexpected and contact support with the full request/response details.
## Historical Age Rules
- Free Forever and Trial plans: up to 1 year of historical data
- Starter: up to 5 years of historical data
- Trader/Quant/Prime: no historical age limit
## mode=cached Rule
`mode=cached` is a paid-plan feature.
- Free/Trial: `mode=cached` is not available and can produce `402`
- Paid plans: available on supported endpoints
For mode details, see [Data Mode](https://www.marketdata.app/docs/api/universal-parameters/mode).
## Premium Endpoint Rule
Free/Trial plans can access standard pricing endpoints but not premium endpoint features. Premium endpoint requests from Free/Trial can produce `402`.
For plan-level capability details, see [Plan Limits](https://www.marketdata.app/docs/account/plan-limits).
## 30-Second Fix Checklist
1. Confirm your current plan in the dashboard.
2. If historical request: reduce date range to your plan limit.
3. If using Free/Trial: remove `mode=cached`.
4. If using Free/Trial: switch from premium endpoint to standard endpoint (or upgrade).
5. Retry after adjusting request scope/feature usage.
## Common Scenarios
### Scenario 1: "I requested 3 years of history on Free/Trial"
What happened:
- Plan allows only 1 year of historical data.
What to do:
- Reduce to `<= 1 year` or upgrade.
### Scenario 2: "I requested 10 years on Starter"
What happened:
- Starter allows up to 5 years of historical data.
What to do:
- Reduce to `<= 5 years` or upgrade to Trader/Quant/Prime.
### Scenario 3: "I used mode=cached on Trial"
What happened:
- Trial does not support cached mode.
What to do:
- Remove `mode=cached` on Trial or move to a paid plan.
### Scenario 4: "I called a premium endpoint on Free/Trial"
What happened:
- Premium endpoint access is not included on Free/Trial.
What to do:
- Use a standard endpoint or upgrade plan.
## When to Contact Support
Contact support if you believe the request should be allowed on your current plan.
Include:
1. Endpoint and full query parameters
2. Date range requested
3. Your current plan
4. Full response payload and headers
---
# Real-Time Data Not Working
Source: https://www.marketdata.app/docs/api/troubleshooting/real-time-data/index.md
If you're expecting real-time data but receiving delayed or historical data instead, this guide will help you diagnose and fix the issue. The API doesn't throw errors when you're missing required agreements or profile information—instead, it silently provides you with the freshest data you're entitled to receive.
## Understanding the Problem
### What You're Experiencing
When you request real-time data (or use `mode=live`), you might be receiving:
- **15-minute delayed data** instead of real-time quotes
- **Historical data (1 day old)** instead of current market data
- Data that appears "stale" when you check the timestamps
### Why This Happens
The API determines what type of data you can receive based on two key factors:
1. **Your Professional Status**: Whether you're classified as a professional or non-professional user
2. **Your Exchange Entitlements**: Whether you've signed the required exchange agreements (IEX, UTP, OPRA)
If your account profile is incomplete or you haven't signed the required exchange agreements, the API cannot determine your proper status or activate your entitlements. In these cases, the API **silently falls back** to the most restrictive data type you're allowed to receive—typically historical data that's at least 1 day old.
### How to Recognize It
The API response includes an `updated` field (or `date` field in some endpoints) that shows when the data was last updated. If this timestamp is more than a few minutes old during market hours, you're likely receiving delayed data instead of real-time data.
## Diagnosing the Issue
### Step 1: Check the Data Timestamp
The first step is to verify whether you're actually receiving real-time data by checking the `updated` field in your API responses.
### Python
```python
from marketdata import MarketDataClient, OutputFormat, MarketDataClientErrorResult
from datetime import datetime
# Make a request for stock quotes
client = MarketDataClient()
quotes = client.stocks.quotes("AAPL", output_format=OutputFormat.JSON)
if isinstance(quotes, MarketDataClientErrorResult):
print(f"Error: {quotes.error}")
elif isinstance(quotes, dict) and quotes.get("s") == "ok" and "updated" in quotes:
# Get the timestamp (Unix epoch seconds)
updated_timestamp = quotes["updated"][0]
updated_time = datetime.fromtimestamp(updated_timestamp)
current_time = datetime.now()
# Calculate the age of the data
age_seconds = (current_time - updated_time).total_seconds()
age_minutes = age_seconds / 60
print(f"Data timestamp: {updated_time}")
print(f"Current time: {current_time}")
print(f"Data age: {age_minutes:.1f} minutes")
# Determine data type
if age_minutes < 1:
print("✅ Real-time data (less than 1 minute old)")
elif age_minutes < 20:
print("⚠️ 15-minute delayed data")
else:
print("❌ Historical data (1 day old or more)")
```
### JavaScript
```js title="checkDataAge.js"
import { MarketDataClient, Mode } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
// Request live data, then inspect the `updated` timestamp.
const quotes = await client.stocks.quotes("AAPL", { mode: Mode.LIVE });
const quote = quotes[0];
if (quote?.updated) {
// `updated` is a Unix timestamp in seconds.
const updatedTime = new Date(quote.updated * 1000);
const currentTime = new Date();
// Calculate the age of the data
const ageMinutes = (currentTime - updatedTime) / 1000 / 60;
console.log(`Data timestamp: ${updatedTime}`);
console.log(`Current time: ${currentTime}`);
console.log(`Data age: ${ageMinutes.toFixed(1)} minutes`);
// Determine data type
if (ageMinutes < 1) {
console.log("✅ Real-time data (less than 1 minute old)");
} else if (ageMinutes < 20) {
console.log("⚠️ 15-minute delayed data");
} else {
console.log("❌ Historical data (1 day old or more)");
}
}
} catch (error) {
console.error(error);
}
```
### TypeScript
```typescript title="checkDataAge.ts"
import { MarketDataClient, Mode } from "@marketdata/sdk";
import type { StockQuote } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
// Request live data, then inspect the `updated` timestamp.
const quotes: StockQuote[] = await client.stocks.quotes("AAPL", { mode: Mode.LIVE });
const quote = quotes[0];
if (quote?.updated) {
// `updated` is a Unix timestamp in seconds.
const updatedTime = new Date(quote.updated * 1000);
const currentTime = new Date();
// Calculate the age of the data
const ageMinutes = (currentTime.getTime() - updatedTime.getTime()) / 1000 / 60;
console.log(`Data timestamp: ${updatedTime}`);
console.log(`Current time: ${currentTime}`);
console.log(`Data age: ${ageMinutes.toFixed(1)} minutes`);
// Determine data type
if (ageMinutes < 1) {
console.log("✅ Real-time data (less than 1 minute old)");
} else if (ageMinutes < 20) {
console.log("⚠️ 15-minute delayed data");
} else {
console.log("❌ Historical data (1 day old or more)");
}
}
} catch (error) {
console.error(error);
}
```
### cURL
```bash
curl -X GET "https://api.marketdata.app/v1/stocks/quotes/AAPL/" \
-H "Authorization: Bearer YOUR_TOKEN" | jq '.updated'
```
The `updated` field contains a Unix timestamp. Compare this to the current time to determine if the data is real-time, delayed, or historical.
### Java
```java title="CheckDataAge.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.Mode;
import com.marketdata.sdk.stocks.StockQuote;
import com.marketdata.sdk.stocks.StockQuoteRequest;
import java.time.Duration;
import java.time.ZonedDateTime;
try (MarketDataClient client = new MarketDataClient()) {
StockQuote quote = client.stocks()
.mode(Mode.LIVE)
.quote(StockQuoteRequest.of("AAPL"))
.values().get(0);
ZonedDateTime updated = quote.updated();
double ageMinutes = Duration.between(updated, ZonedDateTime.now(updated.getZone())).toSeconds() / 60.0;
System.out.printf("Data timestamp: %s%n", updated);
System.out.printf("Data age: %.1f minutes%n", ageMinutes);
if (ageMinutes < 1) {
System.out.println("✅ Real-time data (less than 1 minute old)");
} else if (ageMinutes < 20) {
System.out.println("⚠️ 15-minute delayed data");
} else {
System.out.println("❌ Historical data (1 day old or more)");
}
}
```
### Kotlin
```kotlin title="CheckDataAge.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.Mode
import com.marketdata.sdk.stocks.StockQuoteRequest
import java.time.Duration
import java.time.ZonedDateTime
MarketDataClient().use { client ->
val quote = client.stocks()
.mode(Mode.LIVE)
.quote(StockQuoteRequest.of("AAPL"))
.values()[0]
val updated = quote.updated() ?: error("No timestamp in response")
val ageMinutes = Duration.between(updated, ZonedDateTime.now(updated.zone)).toSeconds() / 60.0
println("Data timestamp: $updated")
println("Data age: %.1f minutes".format(ageMinutes))
when {
ageMinutes < 1 -> println("✅ Real-time data (less than 1 minute old)")
ageMinutes < 20 -> println("⚠️ 15-minute delayed data")
else -> println("❌ Historical data (1 day old or more)")
}
}
```
### C#
```csharp title="CheckDataAge.cs"
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
var quote = (await client.Stocks.GetQuoteAsync(
"AAPL",
options: new MarketDataRequestOptions { Mode = Mode.Live })).Values[0];
// Updated is already normalized to America/New_York; compare in UTC.
var ageMinutes = (DateTimeOffset.UtcNow - quote.Updated!.Value.ToUniversalTime()).TotalMinutes;
Console.WriteLine($"Data timestamp: {quote.Updated}");
Console.WriteLine($"Data age: {ageMinutes:F1} minutes");
if (ageMinutes < 1)
{
Console.WriteLine("✅ Real-time data (less than 1 minute old)");
}
else if (ageMinutes < 20)
{
Console.WriteLine("⚠️ 15-minute delayed data");
}
else
{
Console.WriteLine("❌ Historical data (1 day old or more)");
}
```
### Step 2: Check Your Account Status
After verifying that you're receiving delayed data, check your account status in the [Market Data Dashboard](https://dashboard.marketdata.app/marketdata/):
1. **Log into your account** at the dashboard
2. **Navigate to the Profile section**
3. **Check for incomplete information**:
- Missing contact information (name, address, email, phone)
- Missing employer information (company name, address, job title, LinkedIn)
4. **Check for unsigned agreements**:
- IEX agreement (for real-time stock quotes)
- UTP agreement (for 15-minute delayed stock candles)
- OPRA agreement (for real-time options quotes)
## Root Causes
### 1. Incomplete Data Use Profile
**Problem**: Your account profile is missing required information that prevents us from classifying you as either a professional or non-professional user.
**Required Information**:
- **Contact Information**: Name, Address, Email, Phone Number
- **Employer Information**: Company Name, Address, Job Title, LinkedIn Profile URL
**Impact**: When your profile is incomplete, your professional status is classified as "Unknown." Accounts with unknown status receive the most restrictive data type—historical data that's at least 1 day old—regardless of which exchange agreements you've signed.
**How to Fix**: Complete all required fields in your profile at the [Market Data Dashboard](https://dashboard.marketdata.app/marketdata/). Once your profile is complete, we can classify your account and activate appropriate data access.
### 2. Missing Exchange Agreements
**Problem**: You haven't signed the required exchange agreements that entitle you to real-time or delayed data.
**Required Agreements**:
- **IEX Agreement**: Required for real-time stock quotes
- Without IEX: Non-professional users get 15-minute delayed quotes
- Professional users do not receive real-time quotes with or without IEX. Delayed / historical access depends on the data policies of each exchange.
- **UTP Agreement**: Required for 15-minute delayed stock candles
- Without UTP: All users get historical (1 day old) candles
- **OPRA Agreement**: Required for real-time options quotes
- Without OPRA: Non-professional users get 15-minute delayed options quotes
- Professional users do not receive real-time options quotes with or without OPRA. Delayed / historical access depends on the data policies of each exchange.
**Impact**: Even if your profile is complete and you're classified as non-professional, missing exchange agreements will prevent you from receiving real-time data.
**How to Fix**: Sign the appropriate exchange agreements in your dashboard:
1. Log into the [Market Data Dashboard](https://dashboard.marketdata.app/marketdata/)
2. Navigate to the Profile section
3. Review and sign the required agreements for the data you need
4. Wait for approval (typically processed within 1 business day)
### 3. Unknown Professional Status
**Problem**: Your account cannot be classified as professional or non-professional due to incomplete information.
**Impact**: Accounts with unknown status receive historical data (1 day old) regardless of:
- Which exchange agreements you've signed
- What plan you're on
- Whether you're requesting real-time data
**How to Fix**: Complete your data use profile with all required information. Once we can verify your information and classify your account, we can activate the appropriate data access level.
## Solutions
### Solution 1: Complete Your Data Use Profile
**Steps**:
1. Log into the [Market Data Dashboard](https://dashboard.marketdata.app/marketdata/)
2. Navigate to **Profile** section
3. Ensure all required fields are completed:
- **Contact Information**: Name, Address, Email, Phone Number
- **Employer Information**: Company Name, Address, Job Title, LinkedIn Profile URL
4. Submit the profile for verification
**Verification Process**:
- We review your account profile and verify information against third-party databases (LinkedIn, FINRA BrokerCheck)
- The goal is to classify your data use as either professional or non-professional
- Verification typically takes **1 business day**
- Until verification is complete, your account cannot be activated for real-time data
**Note**: Customers employed by banks, insurance companies, or other financial institutions may require additional review.
### Solution 2: Sign Exchange Agreements
**Steps**:
1. Log into the [Market Data Dashboard](https://dashboard.marketdata.app/marketdata/)
2. Navigate to **Profile** section
3. Review and sign the required agreements:
- **IEX Agreement**: For real-time stock quotes
- **UTP Agreement**: For 15-minute delayed stock candles
- **OPRA Agreement**: For real-time options quotes
4. Wait for approval (typically **1 business day**)
**Important**: You must have a complete profile before exchange agreements can be activated. If your profile is incomplete, signing agreements won't enable real-time data access.
### Solution 3: Wait for Verification
After completing your profile and signing agreements:
- Allow **1 business day** for verification and approval
- Check your dashboard for status updates
- Test your API requests after verification is complete
## Data Availability by Endpoint
The data type you receive (real-time, 15-minute delayed, or historical) depends on your user type, exchange entitlements, and the specific endpoint you're using. **Professional users do not get real-time data on all endpoints**, but the exact data type available varies by endpoint.
**To determine what data types are available for your user classification**, check the **Data Availability** section in the documentation for each endpoint you're using:
**Stocks Endpoints:**
- **[Stock Quotes](https://www.marketdata.app/docs/api/stocks/quotes#data-availability)** (`/v1/stocks/quotes/`)
- **[Stock Prices](https://www.marketdata.app/docs/api/stocks/prices#data-availability)** (`/v1/stocks/prices/`)
- **[Stock Candles](https://www.marketdata.app/docs/api/stocks/candles#data-availability)** (`/v1/stocks/candles/`)
- **[Bulk Stock Candles](https://www.marketdata.app/docs/api/stocks/bulkcandles#data-availability)** (`/v1/stocks/bulkcandles/`)
- **[Stock News](https://www.marketdata.app/docs/api/stocks/news#data-availability)** (`/v1/stocks/news/`)
- **[Stock Earnings](https://www.marketdata.app/docs/api/stocks/earnings#data-availability)** (`/v1/stocks/earnings/`)
**Options Endpoints:**
- **[Options Quotes](https://www.marketdata.app/docs/api/options/quotes#data-availability)** (`/v1/options/quotes/`)
- **[Option Chain](https://www.marketdata.app/docs/api/options/chain#data-availability)** (`/v1/options/chain/`)
- **[Option Lookup](https://www.marketdata.app/docs/api/options/lookup#data-availability)** (`/v1/options/lookup/`)
- **[Option Expirations](https://www.marketdata.app/docs/api/options/expirations#data-availability)** (`/v1/options/expirations/`)
Each endpoint's Data Availability section provides the canonical source of truth for what data types are available for professional users, non-professional users, and users with unknown status, along with the required exchange entitlements.
## Verification Steps
### How to Verify Your Profile is Complete
1. Log into the [Market Data Dashboard](https://dashboard.marketdata.app/marketdata/)
2. Navigate to **Profile** section
3. Check that all fields are filled:
- ✅ Name
- ✅ Address
- ✅ Email
- ✅ Phone Number
- ✅ Company Name (if applicable)
- ✅ Company Address (if applicable)
- ✅ Job Title (if applicable)
- ✅ LinkedIn Profile URL (if applicable)
If any fields are missing, complete them and submit your profile.
### How to Verify Agreements are Signed
1. Log into the [Market Data Dashboard](https://dashboard.marketdata.app/marketdata/)
2. Navigate to **Profile** section
3. Look for agreement status indicators:
- ✅ IEX Agreement (for stock quotes)
- ✅ UTP Agreement (for stock candles)
- ✅ OPRA Agreement (for options quotes)
If agreements show as unsigned, review and sign them. Note that agreements may take up to 1 business day to be approved and activated.
### How to Test if Real-Time Data is Working
After completing your profile and signing agreements, wait 1 business day for verification, then test:
### Python
```python
from marketdata import MarketDataClient, OutputFormat, MarketDataClientErrorResult
from datetime import datetime, timezone
def test_realtime_data(symbol="AAPL"):
"""Test if real-time data is working for a given symbol."""
client = MarketDataClient()
quotes = client.stocks.quotes(symbol, output_format=OutputFormat.JSON)
if isinstance(quotes, MarketDataClientErrorResult):
print(f"Error: {quotes.error}")
return False
if not isinstance(quotes, dict) or quotes.get("s") != "ok":
print(f"Error: {quotes.get('errmsg', 'Unknown error') if isinstance(quotes, dict) else 'Invalid response'}")
return False
if "updated" not in quotes:
print("Warning: No timestamp in response")
return False
# Get timestamp
updated_timestamp = quotes["updated"][0]
updated_time = datetime.fromtimestamp(updated_timestamp, tz=timezone.utc)
current_time = datetime.now(timezone.utc)
# Calculate age
age_seconds = (current_time - updated_time).total_seconds()
age_minutes = age_seconds / 60
print(f"Symbol: {symbol}")
print(f"Data timestamp: {updated_time}")
print(f"Current time: {current_time}")
print(f"Data age: {age_minutes:.2f} minutes")
# Check if real-time (less than 1 minute old during market hours)
if age_minutes < 1:
print("✅ Real-time data is working!")
return True
elif age_minutes < 20:
print("⚠️ Receiving 15-minute delayed data")
print(" → Check if IEX agreement is signed and approved")
return False
else:
print("❌ Receiving historical data (1 day old)")
print(" → Check if profile is complete and agreements are signed")
return False
# Test during market hours for best results
test_realtime_data("AAPL")
```
### JavaScript
```js title="testRealtimeData.js"
import { MarketDataClient, Mode } from "@marketdata/sdk";
const client = new MarketDataClient();
async function testRealtimeData(symbol = "AAPL") {
try {
const quotes = await client.stocks.quotes(symbol, { mode: Mode.LIVE });
const quote = quotes[0];
if (!quote?.updated) {
console.log("Warning: No timestamp in response");
return false;
}
// `updated` is a Unix timestamp in seconds.
const updatedTime = new Date(quote.updated * 1000);
const currentTime = new Date();
// Calculate age
const ageMinutes = (currentTime - updatedTime) / 1000 / 60;
console.log(`Symbol: ${symbol}`);
console.log(`Data timestamp: ${updatedTime.toISOString()}`);
console.log(`Current time: ${currentTime.toISOString()}`);
console.log(`Data age: ${ageMinutes.toFixed(2)} minutes`);
// Check if real-time (less than 1 minute old during market hours)
if (ageMinutes < 1) {
console.log("✅ Real-time data is working!");
return true;
} else if (ageMinutes < 20) {
console.log("⚠️ Receiving 15-minute delayed data");
console.log(" → Check if IEX agreement is signed and approved");
return false;
} else {
console.log("❌ Receiving historical data (1 day old)");
console.log(" → Check if profile is complete and agreements are signed");
return false;
}
} catch (error) {
console.error(`Error: ${error.message}`);
return false;
}
}
// Test during market hours for best results
testRealtimeData("AAPL");
```
### TypeScript
```typescript title="testRealtimeData.ts"
import { MarketDataClient, Mode } from "@marketdata/sdk";
import type { StockQuote } from "@marketdata/sdk";
const client = new MarketDataClient();
async function testRealtimeData(symbol: string = "AAPL"): Promise {
try {
const quotes: StockQuote[] = await client.stocks.quotes(symbol, { mode: Mode.LIVE });
const quote = quotes[0];
if (!quote?.updated) {
console.log("Warning: No timestamp in response");
return false;
}
// `updated` is a Unix timestamp in seconds.
const updatedTime = new Date(quote.updated * 1000);
const currentTime = new Date();
// Calculate age
const ageMinutes = (currentTime.getTime() - updatedTime.getTime()) / 1000 / 60;
console.log(`Symbol: ${symbol}`);
console.log(`Data timestamp: ${updatedTime.toISOString()}`);
console.log(`Current time: ${currentTime.toISOString()}`);
console.log(`Data age: ${ageMinutes.toFixed(2)} minutes`);
// Check if real-time (less than 1 minute old during market hours)
if (ageMinutes < 1) {
console.log("✅ Real-time data is working!");
return true;
} else if (ageMinutes < 20) {
console.log("⚠️ Receiving 15-minute delayed data");
console.log(" → Check if IEX agreement is signed and approved");
return false;
} else {
console.log("❌ Receiving historical data (1 day old)");
console.log(" → Check if profile is complete and agreements are signed");
return false;
}
} catch (error) {
console.error(`Error: ${(error as Error).message}`);
return false;
}
}
// Test during market hours for best results
testRealtimeData("AAPL");
```
### Java
```java title="TestRealtimeData.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.Mode;
import com.marketdata.sdk.stocks.StockQuote;
import com.marketdata.sdk.stocks.StockQuoteRequest;
import java.time.Duration;
import java.time.ZonedDateTime;
static boolean testRealtimeData(MarketDataClient client, String symbol) {
StockQuote quote = client.stocks()
.mode(Mode.LIVE)
.quote(StockQuoteRequest.of(symbol))
.values().get(0);
ZonedDateTime updated = quote.updated();
if (updated == null) {
System.out.println("Warning: no timestamp in response");
return false;
}
double ageMinutes = Duration.between(updated, ZonedDateTime.now(updated.getZone())).toSeconds() / 60.0;
System.out.printf("%s data age: %.2f minutes%n", symbol, ageMinutes);
if (ageMinutes < 1) {
System.out.println("✅ Real-time data is working!");
return true;
} else if (ageMinutes < 20) {
System.out.println("⚠️ Receiving 15-minute delayed data — check the IEX agreement");
return false;
} else {
System.out.println("❌ Receiving historical data — complete your profile and agreements");
return false;
}
}
// Test during market hours for best results
testRealtimeData(client, "AAPL");
```
### Kotlin
```kotlin title="TestRealtimeData.kt"
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.Mode
import com.marketdata.sdk.stocks.StockQuoteRequest
import java.time.Duration
import java.time.ZonedDateTime
fun testRealtimeData(client: MarketDataClient, symbol: String = "AAPL"): Boolean {
val quote = client.stocks()
.mode(Mode.LIVE)
.quote(StockQuoteRequest.of(symbol))
.values()[0]
val updated = quote.updated() ?: run {
println("Warning: no timestamp in response")
return false
}
val ageMinutes = Duration.between(updated, ZonedDateTime.now(updated.zone)).toSeconds() / 60.0
println("$symbol data age: %.2f minutes".format(ageMinutes))
return when {
ageMinutes < 1 -> { println("✅ Real-time data is working!"); true }
ageMinutes < 20 -> { println("⚠️ Receiving 15-minute delayed data — check the IEX agreement"); false }
else -> { println("❌ Receiving historical data — complete your profile and agreements"); false }
}
}
```
### C#
```csharp title="TestRealtimeData.cs"
using MarketDataApp;
static async Task TestRealtimeDataAsync(MarketDataClient client, string symbol)
{
var quote = (await client.Stocks.GetQuoteAsync(
symbol,
options: new MarketDataRequestOptions { Mode = Mode.Live })).Values[0];
if (quote.Updated is not { } updated)
{
Console.WriteLine("Warning: no timestamp in response");
return false;
}
var ageMinutes = (DateTimeOffset.UtcNow - updated.ToUniversalTime()).TotalMinutes;
Console.WriteLine($"{symbol} data age: {ageMinutes:F2} minutes");
if (ageMinutes < 1)
{
Console.WriteLine("✅ Real-time data is working!");
return true;
}
if (ageMinutes < 20)
{
Console.WriteLine("⚠️ Receiving 15-minute delayed data — check the IEX agreement");
return false;
}
Console.WriteLine("❌ Receiving historical data — complete your profile and agreements");
return false;
}
// Test during market hours for best results
await TestRealtimeDataAsync(client, "AAPL");
```
## Common Scenarios
### Scenario 1: "I just signed up but I'm still getting delayed data"
**What's happening**: Your account profile is likely incomplete, or you haven't signed the required exchange agreements yet.
**Solution**:
1. Complete your data use profile with all required information
2. Sign the appropriate exchange agreements (IEX for stock quotes, OPRA for options)
3. Wait 1 business day for verification and approval
4. Test your API requests after verification
**Why it takes time**: We need to verify your information against third-party databases and get exchange approval for your agreements. This process typically takes 1 business day.
### Scenario 2: "I'm on a paid plan (Trader/Quant/Prime) but not getting real-time data"
**What's happening**: Having a paid plan alone doesn't guarantee real-time data. You also need:
- A complete data use profile (so we can classify your account)
- Signed exchange agreements (IEX, UTP, OPRA)
- Non-professional classification (professional users get delayed/historical data)
**Solution**:
1. Check your profile in the dashboard—is it complete?
2. Check your agreements—are they signed and approved?
3. Check your professional status in the [customer dashboard](https://dashboard.marketdata.app/marketdata/profile)—if you're classified as professional, you'll receive delayed/historical data regardless of agreements, and real-time access requires exchange licensing through our [sales team](https://www.marketdata.app/contact/)
**Note**: Professional users (corporations, investment advisors, money managers, etc.) do not receive real-time data for compliance reasons, even with signed agreements. No self-service plan and no combination of signed agreements grants a professional user real-time exchange data. Delayed / historical data access remains available, depending on the data policies of each exchange. Real-time access for professional use requires direct exchange licensing — [contact our sales team](https://www.marketdata.app/contact/) to discuss it.
### Scenario 3: "My data was real-time yesterday but delayed today"
**What's happening**: This could indicate:
- An agreement expired or was revoked
- Your account status changed (e.g., reclassified as professional)
- A verification issue that needs attention
**Solution**:
1. Check your dashboard for any status changes or notifications
2. Verify your agreements are still signed and approved
3. Check if your profile information is still complete
4. Contact support if the issue persists
### Scenario 4: "How do I know if I'm professional or non-professional?"
**Professional users** are typically:
- Corporations or business entities
- Investment advisors
- Money managers
- Users who trade on behalf of others
**Non-professional users** are typically:
- Individual investors trading for personal accounts
- Not employed by financial institutions
- Not trading on behalf of others
**How to check**: You can check your professional status in the [customer dashboard](https://dashboard.marketdata.app/marketdata/profile). Your classification is determined during the verification process when you complete your profile. If you're unsure about your status, contact support for clarification.
**Impact**: Professional users do not receive real-time data regardless of exchange agreements, while non-professional users can access real-time data with proper entitlements. Delayed / historical data access remains available, depending on the data policies of each exchange. If you are classified as professional and need real-time data, it requires direct exchange licensing — [contact our sales team](https://www.marketdata.app/contact/) to discuss it.
### Scenario 5: "I completed my profile and signed agreements, but still getting delayed data"
**What's happening**: The verification and approval process may not be complete yet, or there may be an issue with your information.
**Solution**:
1. **Wait 1 business day** after completing your profile and signing agreements
2. Check your dashboard for any notifications or status updates
3. Verify your information matches third-party databases (LinkedIn, etc.)
4. If information doesn't match, update your profile to match your LinkedIn or other professional profiles
5. Contact support if more than 1 business day has passed
**Common issues**:
- Profile information doesn't match LinkedIn or other databases
- Missing required fields that weren't obvious
- Additional verification needed for financial institution employees
## Getting Help
### When to Contact Support
Contact our support team if:
- You've completed your profile and signed agreements but still receiving delayed data after 1 business day
- You're unsure about your professional status classification
- Your profile information matches third-party databases but verification is still pending
- You need clarification on which agreements you need for your use case
- You've followed all steps but are still experiencing issues
### What to Include in Your Support Ticket
To help us resolve your issue quickly, please include:
1. **Your account information**:
- Email address associated with your account
- Plan type (Free, Starter, Trader, Quant, Prime)
2. **Diagnosis information**:
- Example API request that's returning delayed data
- The `updated` timestamp from a recent response
- Current time when you made the request
- Calculated age of the data (in minutes)
3. **Account status**:
- Whether your profile is complete (all fields filled)
- Which agreements you've signed (IEX, UTP, OPRA)
- When you completed your profile and signed agreements
- Any error messages or notifications from the dashboard
4. **Test results**:
- Output from the verification test code (if you ran it)
- Screenshots of your dashboard profile/agreement status (if helpful)
### Support Resources
- **Dashboard**: [Market Data Dashboard](https://dashboard.marketdata.app/marketdata/)
- **Entitlements Documentation**: [Exchange Entitlements](https://www.marketdata.app/docs/account/entitlements)
- **Verification Documentation**: [Account Verification](https://www.marketdata.app/docs/account/data-policies/account-verification)
## Summary
Real-time data requires:
1. ✅ **Complete data use profile** (contact + employer information)
2. ✅ **Signed exchange agreements** (IEX for stocks, OPRA for options, UTP for candles)
3. ✅ **Non-professional classification** (professional users get delayed/historical data)
4. ✅ **Verification approval** (typically 1 business day)
Professional users cannot satisfy requirement 3 on a self-service plan. Real-time exchange data for professional use requires direct exchange licensing — [contact our sales team](https://www.marketdata.app/contact/) to discuss it.
The API doesn't throw errors when these requirements aren't met—it silently provides the best data you're entitled to receive. Check the `updated` timestamp in your API responses to verify you're getting real-time data, and use this guide to diagnose and fix any issues.
---
# 429: Credit Limit Reached
Source: https://www.marketdata.app/docs/api/troubleshooting/running-out-of-credits/index.md
A `429 TOO MANY REQUESTS` can be returned for two different reasons:
- Credit limit reached
- Too many concurrent requests
This page is only for the **credit limit** case. If your issue is concurrency, use [429: Too Many Concurrent Requests](https://www.marketdata.app/docs/api/troubleshooting/too-many-concurrent-requests).
For full policy details, see [Rate Limits](https://www.marketdata.app/docs/api/rate-limiting).
## Fast Triage
Use the response message and headers to identify which 429 you hit.
Typical credit-limit error payload:
```json
{
"s": "error",
"errmsg": "API credit limit reached for current window.",
"troubleshootingGuide": "https://www.marketdata.app/docs/api/troubleshooting/running-out-of-credits"
}
```
**Credit-limit 429 indicators:**
- Error message indicates credit/rate budget exhaustion for your plan window
- `X-Api-Ratelimit-Remaining` is `0` (or effectively exhausted)
**Concurrency 429 indicators:**
- Error message indicates too many simultaneous requests
- Usually occurs during bursts with many in-flight requests
- Official SDKs handle concurrency automatically and maximize throughput up to the 50-request limit without exceeding it
## 30-Second Action Checklist
1. Confirm this is a credit-limit 429 (not concurrency).
2. Read `X-Api-Ratelimit-Remaining` and `X-Api-Ratelimit-Reset`.
3. Find your plan group below and apply its primary workflow.
4. Check `X-Api-Ratelimit-Consumed` on recent heavy requests.
5. If still blocked repeatedly after optimization, escalate (support or plan change).
## Plan Groups
Troubleshooting should follow your plan's optimization capabilities.
| Plan Group | Plans | Limit Window | `mode=cached` Available |
|------------------|-------------------------------------------|--------------|-------------------------|
| Free + Trials | Free Forever, Starter Trial, Trader Trial | Daily | No |
| Starter + Trader | Starter, Trader | Daily | Yes |
| Quant + Prime | Quant, Prime | Per-minute | Yes |
> [!WARNING]
> **[Trial plan limitation]**
>
> `mode=cached` is **not available** on Starter Trial and Trader Trial.
> [!CAUTION]
> **[Reset Window Gotcha (All Daily-Reset Plans)]**
>
> All plans with daily credit windows (Free Forever, Starter Trial, Trader Trial, Starter, Trader) reset at **9:30 AM Eastern Time**, not midnight.
>
> This is intentional safety behavior: if a script runs out of control overnight, the reset timing limits impact to one trading day budget instead of potentially consuming two trading-day budgets across a midnight boundary.
## Plan Playbooks
### Free Forever + Trial Plans (No Cached Mode)
These plans cannot use cached mode. For bulk option use, use one of these workflows:
1. **One-step server-side filtering:** keep quote columns and aggressively reduce contracts on the chain request using filters such as `strikeLimit`, `expiration`, `type`, `strike`, `minBid`, and `maxBid`.
2. **Two-step local filtering:** pull the full/large chain without quote columns, apply your own logic locally, then request quotes only for selected contracts using `options/quotes`.
If current-day quote freshness is not required, a third strategy can be cheaper: use **historical option chains** with the `date` parameter. Historical chains are billed at **1 credit per 1,000 option symbols**, while real-time/15-minute delayed quote chains are billed per symbol.
```http
# One-step: server-side liquidity + contract filters (quotes included)
GET https://api.marketdata.app/v1/options/chain/UNDERLYING/?expiration=YYYY-MM-DD&type=call&strikeLimit=5&minBid=0.50&maxBid=10&columns=symbol,strike,expiration,type,bid,ask,mid,last,openInterest,volume
# Two-step (Step 1): broad chain without costly quote columns
GET https://api.marketdata.app/v1/options/chain/UNDERLYING/?expiration=all&columns=symbol,strike,expiration,type,openInterest,volume
# Two-step (Step 2): quotes only for selected contracts
GET https://api.marketdata.app/v1/options/quotes/OPTION_SYMBOL/
# Historical chain example (lower-cost bulk snapshot)
GET https://api.marketdata.app/v1/options/chain/UNDERLYING/?date=YYYY-MM-DD&expiration=all
```
If you are still getting credit-limit 429 after these optimizations, the primary path is to upgrade to a paid plan.
### Starter + Trader (Daily Limits + Cached Mode)
Primary optimization for this plan group is `mode=cached` on bulk requests.
Recommended process:
1. Request bulk data with `mode=cached` (optionally with `maxage`).
2. Inspect the `updated` field in the response to measure snapshot age.
3. If fresh enough, use cached data.
4. If too old, re-request only required contracts/symbols in live mode instead of re-pulling the full chain.
```http
# Step 1: bulk chain from cache (cheap, 1 credit)
GET https://api.marketdata.app/v1/options/chain/UNDERLYING/?mode=cached&maxage=5min&expiration=YYYY-MM-DD
# Step 2: only if selected contracts are too stale per `updated`, request live quotes for those contracts
GET https://api.marketdata.app/v1/options/quotes/OPTION_SYMBOL/
```
Additional controls for Starter + Trader:
1. Filter option chains aggressively (`strikeLimit`, `expiration`, `type`, `strike`, liquidity filters).
2. Exclude `bid`, `ask`, `mid`, `last` when current pricing is not needed.
3. Add daily credit guardrails to avoid getting blocked for the rest of the day.
### Quant + Prime (Per-Minute Limits + Cached Mode)
Use cached-mode bulk strategy and filter optimization, plus minute-window pacing:
1. Smooth burst traffic (queue + worker pool)
2. Pace large chain pulls across the minute window
3. Prefer cached mode for full-chain or broad bulk pulls
Quant/Prime have no daily credit cap. 429s here are the result of per-minute bursts, not daily depletion.
## How Pricing Actually Works
The mental model `1 request = 1 credit` is often wrong for bulk and candles workloads.
### Common baseline
- Many non-bulk responses: `1 request = 1 credit`
### High-usage endpoints where pricing scales
1. `options/chain`
- Real-time or delayed quote chains can consume per option symbol returned when quote fields are included.
- Historical chain requests (`date` parameter) are billed at 1 credit per 1,000 option symbols.
2. `stocks/quotes`
- Multi-symbol quote responses with quote fields can consume per symbol returned.
3. `stocks/prices`
- Multi-symbol price/quote responses with quote fields can consume per symbol returned.
4. `stocks/bulkcandles`
- Bulk candle responses can consume per symbol returned.
5. `stocks/candles`
- Candle responses are billed at 1 credit per 1,000 candles, so large ranges can consume multiple credits in one request.
Practical rule: credit usage scales with response size on bulk quote endpoints and long candle responses.
### High-impact example
```http
# Full live chain can consume very large credits
GET https://api.marketdata.app/v1/options/chain/UNDERLYING/?expiration=all
# Filtered chain reduces returned symbols
GET https://api.marketdata.app/v1/options/chain/UNDERLYING/?expiration=all&strikeLimit=1
# Cached mode (paid plans only) reduces credits to 1
GET https://api.marketdata.app/v1/options/chain/UNDERLYING/?mode=cached
```
For cached mode details, see [mode parameter](https://www.marketdata.app/docs/api/universal-parameters/mode).
## Diagnose With Headers
Check these headers on every response:
- `X-Api-Ratelimit-Consumed`: credits consumed by current request
- `X-Api-Ratelimit-Remaining`: credits remaining in current window
- `X-Api-Ratelimit-Limit`: total credits for current window
- `X-Api-Ratelimit-Reset`: reset time in UTC epoch seconds
```bash
curl -i -X GET "https://api.marketdata.app/v1/stocks/quotes/AAPL/" \
-H "Authorization: Bearer YOUR_TOKEN"
```
## Common Cost Traps
1. Pulling full option chains without filters
2. Including `bid/ask/mid/last` unnecessarily
3. Repeating expensive live requests without caching or deduplication
4. Ignoring rate-limit headers during production runs
## Common Scenarios
### Scenario 1: "I requested one option chain and ran out of credits"
What happened:
- A single `options/chain` request returned a very large number of contracts.
- With quote fields included, credits scaled by returned contracts.
What to do:
- Free + Trials: use server-side filters or two-step local filtering; consider historical chains for bulk snapshots.
- Starter + Trader and Quant + Prime: use `mode=cached` for bulk pulls, then selectively refresh only contracts you need live.
### Scenario 2: "I made a small number of requests but used far more credits than expected"
What happened:
- Requests hit bulk endpoints (`options/chain`, `stocks/quotes`, `stocks/prices`) with quote fields.
- Credit usage scaled with symbols/contracts returned, not request count.
What to do:
- Inspect `X-Api-Ratelimit-Consumed` for high-cost requests.
- Remove unnecessary quote fields or reduce returned symbols/contracts with filters.
### Scenario 3: "My credits did not reset at midnight"
What happened:
- You expected a calendar-day reset.
- Daily-limit plans reset at **9:30 AM Eastern Time**, not midnight.
What to do:
- Use `X-Api-Ratelimit-Reset` as source of truth for exact reset timing.
- Align scheduler logic to `America/New_York` and the 9:30 AM ET window.
### Scenario 4: "I need bulk option data every day on Free/Trial"
What happened:
- No access to `mode=cached` on Free/Trial makes full quote-chain pulls expensive.
What to do:
- Use one-step server-side liquidity/contract filters or two-step local filtering.
- If intraday quote freshness is not required, prefer historical chain snapshots (`date`) for lower credit burn.
- If this remains insufficient, upgrade to a paid plan.
### Scenario 5: "I am on Starter/Trader and still getting blocked daily"
What happened:
- Cached mode is not being used as primary bulk strategy, or stale cache fallback is too broad (full live repulls).
What to do:
- Make `mode=cached` the default bulk path.
- Check `updated` freshness and only re-request live data for required contracts/symbols.
- Add daily guardrails to stop or degrade workload before hard-limit exhaustion.
## When to Contact Support
Open a support ticket if you have optimized for your plan group and still cannot operate within limits.
Include:
1. Recent `X-Api-Ratelimit-*` header values
2. Sample expensive requests
3. Your current plan
4. Expected request volume and interval
---
# Service Outages
Source: https://www.marketdata.app/docs/api/troubleshooting/service-outages/index.md
Market Data, as stated in our [terms of service](https://www.marketdata.app/terms/), makes no representation as to the reliability, availability, or timeliness of our service. **This is not just a standard disclaimer.** We have not yet been able to achieve 99.9% reliability, which is a metric we consider a minimum level of reliability that is needed to operate without a backup provider.
**Market Data is a low cost provider** and we have determined that cost, rather than reliability, is our key driver. While we hope to achieve 99.9% reliability in the future, our focus will remain on keeping down costs and avoiding price increases for our users.
> [!TIP]
> **[Recommendation]**
>
> We highly encourage users with mission critical applications to have a backup provider or utilize Market Data as their secondary provider.
## How To Confirm Downtime
Market Data provides real-time status monitoring for each of our API endpoints. You can check the status of individual endpoints either through our status webpage or programmatically via our status API endpoint.
### Status Webpage
Visit our [status page](https://www.marketdata.app/status/) to view the current status of all Market Data API endpoints, including historical uptime statistics.
- Status Page: [https://www.marketdata.app/status/](https://www.marketdata.app/status/)
### Confirm Downtime Programmatically
Use the [status endpoint](https://www.marketdata.app/docs/api/utilities/status) at `https://api.marketdata.app/status/` to programmatically check the status of all Market Data API endpoints. This endpoint returns the current status for each endpoint (e.g., `/v1/stocks/quotes/`, `/v1/options/chain/`, etc.) and will remain online during outages, allowing you to determine which specific endpoints are affected. Unlike every other endpoint, the status endpoint is not versioned: it is served at `/status/`, not under `/v1/`.
> [!TIP]
> This endpoint is ideal to allow for automatic switching between Market Data and your backup provider when specific endpoints are down.
### JavaScript
```js title="status.js"
import { MarketDataClient } from "@marketdata/sdk";
const client = new MarketDataClient();
// Look up the status of a specific endpoint in the API status response.
function findEndpointStatus(status, endpointPath) {
const index = status.service.indexOf(endpointPath);
if (index === -1) return null;
return {
online: status.online[index],
status: status.status[index],
uptime30d: status.uptimePct30d?.[index],
uptime90d: status.uptimePct90d?.[index],
};
}
try {
const status = await client.utilities.status();
const stocksQuotes = findEndpointStatus(status, "/v1/stocks/quotes/");
const optionsChain = findEndpointStatus(status, "/v1/options/chain/");
const stocksCandles = findEndpointStatus(status, "/v1/stocks/candles/");
console.log(`Stocks Quotes: ${stocksQuotes ? (stocksQuotes.online ? "Online" : "Offline") : "Not found"}`);
console.log(`Options Chain: ${optionsChain ? (optionsChain.online ? "Online" : "Offline") : "Not found"}`);
console.log(`Stocks Candles: ${stocksCandles ? (stocksCandles.online ? "Online" : "Offline") : "Not found"}`);
} catch (error) {
console.error("Error fetching API status:", error);
}
```
### TypeScript
```typescript title="status.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { ApiStatusResponse } from "@marketdata/sdk";
const client = new MarketDataClient();
// Look up the status of a specific endpoint in the API status response.
function findEndpointStatus(status: ApiStatusResponse, endpointPath: string) {
const index = status.service.indexOf(endpointPath);
if (index === -1) return null;
return {
online: status.online[index],
status: status.status[index],
uptime30d: status.uptimePct30d?.[index],
uptime90d: status.uptimePct90d?.[index],
};
}
try {
const status: ApiStatusResponse = await client.utilities.status();
const stocksQuotes = findEndpointStatus(status, "/v1/stocks/quotes/");
const optionsChain = findEndpointStatus(status, "/v1/options/chain/");
const stocksCandles = findEndpointStatus(status, "/v1/stocks/candles/");
console.log(`Stocks Quotes: ${stocksQuotes ? (stocksQuotes.online ? "Online" : "Offline") : "Not found"}`);
console.log(`Options Chain: ${optionsChain ? (optionsChain.online ? "Online" : "Offline") : "Not found"}`);
console.log(`Stocks Candles: ${stocksCandles ? (stocksCandles.online ? "Online" : "Offline") : "Not found"}`);
} catch (error) {
console.error("Error fetching API status:", error);
}
```
### Python
```python title="status.py"
# Importing the required library
import requests
# URL to the status endpoint
url = "https://api.marketdata.app/status/"
json_data = requests.get(url).json()
# Function to check the status of a specific endpoint
def check_endpoint_status(endpoint_path):
try:
index = json_data["service"].index(endpoint_path)
return {
"online": json_data["online"][index],
"status": json_data["status"][index],
"uptime30d": json_data["uptimePct30d"][index],
"uptime90d": json_data["uptimePct90d"][index]
}
except ValueError:
return None
# Checking the status of specific endpoints
stocks_quotes = check_endpoint_status("/v1/stocks/quotes/")
options_chain = check_endpoint_status("/v1/options/chain/")
stocks_candles = check_endpoint_status("/v1/stocks/candles/")
print(f"Stocks Quotes: {'Online' if stocks_quotes and stocks_quotes['online'] else 'Offline' if stocks_quotes else 'Not found'}")
print(f"Options Chain: {'Online' if options_chain and options_chain['online'] else 'Offline' if options_chain else 'Not found'}")
print(f"Stocks Candles: {'Online' if stocks_candles and stocks_candles['online'] else 'Offline' if stocks_candles else 'Not found'}")
```
### Java
```java title="Status.java"
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.utilities.ServiceStatus;
import java.util.List;
try (MarketDataClient client = new MarketDataClient()) {
List services = client.utilities().status().values();
for (String path : List.of("/v1/stocks/quotes/", "/v1/options/chain/", "/v1/stocks/candles/")) {
String state = services.stream()
.filter(s -> s.service().equals(path))
.findFirst()
.map(s -> s.online() ? "Online" : "Offline")
.orElse("Not found");
System.out.println(path + ": " + state);
}
}
```
### Kotlin
```kotlin title="Status.kt"
import com.marketdata.sdk.MarketDataClient
MarketDataClient().use { client ->
val services = client.utilities().status().values()
for (path in listOf("/v1/stocks/quotes/", "/v1/options/chain/", "/v1/stocks/candles/")) {
val svc = services.firstOrNull { it.service() == path }
val state = svc?.let { if (it.online()) "Online" else "Offline" } ?: "Not found"
println("$path: $state")
}
}
```
### C#
```csharp title="Status.cs"
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
var services = (await client.Utilities.GetStatusAsync()).Values;
foreach (var path in new[] { "/v1/stocks/quotes/", "/v1/options/chain/", "/v1/stocks/candles/" })
{
var service = services.FirstOrDefault(s => s.Service == path);
var state = service is null ? "Not found" : service.Online ? "Online" : "Offline";
Console.WriteLine($"{path}: {state}");
}
```
## What To Do During Downtime
It is not necessary to advise us of downtime or service outages. We monitor the status of our systems and we investigate and respond to all service outages. During Market Data service outages, we encourage you to switch your systems over to your back-up provider until our systems come back online.
---
# 429: Too Many Concurrent Requests
Source: https://www.marketdata.app/docs/api/troubleshooting/too-many-concurrent-requests/index.md
A `429 TOO MANY REQUESTS` can also mean your account exceeded the concurrent request limit.
This page is only for the **concurrency** case. If your issue is credit depletion, use [429: Credit Limit Reached](https://www.marketdata.app/docs/api/troubleshooting/running-out-of-credits).
For full policy details, see [Rate Limits](https://www.marketdata.app/docs/api/rate-limiting).
## Fast Triage
Use error context and rate-limit headers to confirm this is concurrency, not credits.
Typical concurrency-limit error payload:
```json
{
"s": "error",
"errmsg": "Too many concurrent requests. Please reduce parallel requests and retry.",
"troubleshootingGuide": "https://www.marketdata.app/docs/api/troubleshooting/too-many-concurrent-requests"
}
```
**Concurrency 429 indicators:**
- 429 appears during bursts of parallel requests
- You still have credit budget remaining
- Workload fan-out is high (many simultaneous in-flight calls)
**Credit-limit 429 indicators:**
- `X-Api-Ratelimit-Remaining` is `0` (or effectively exhausted)
- Error message indicates plan-window budget exhaustion
## 30-Second Action Checklist
1. Confirm this is a concurrency 429 (not credit depletion).
2. If possible, switch to an official Market Data SDK.
3. If not using SDK, reduce in-flight concurrency immediately.
4. Add retries with exponential backoff + jitter for 429.
5. Verify 429 rate falls after reducing burst fan-out.
## What This 429 Means
All plans are limited to **50 simultaneous in-flight requests**. If your application exceeds that, additional requests return `429` until concurrency drops.
## Primary Fix: Use Official SDKs
Official Market Data SDK clients are designed around the 50-request concurrency limit and automatically maximize throughput up to that limit without exceeding it.
If you are using an SDK and still seeing concurrency 429, check for additional parallelism in your own app layer (multiple workers/processes/instances each issuing SDK calls).
## Non-SDK Playbook
If you are using a custom HTTP client:
1. Cap worker/thread pool below 50.
2. Use bounded queueing to prevent fan-out bursts.
3. Ensure each worker handles one request at a time.
4. Add per-request timeout and retry budget.
5. Log in-flight concurrency and 429 counts.
### Recommended Non-SDK Pattern
1. Fixed max concurrency (for example, 20-40).
2. Shared request queue.
3. Retry with exponential backoff + jitter on 429.
4. Gradual ramp-up instead of burst starts.
## Non-SDK Backoff Examples
These examples are only for custom HTTP clients. If you use an official SDK, skip this section.
### Python
```python
import random
import time
import requests
def request_with_backoff(url, headers, max_retries=5):
for attempt in range(max_retries + 1):
response = requests.get(url, headers=headers, timeout=30)
if response.status_code != 429:
return response
backoff = 0.2 * (2 ** attempt)
jitter = random.uniform(0, 0.1)
time.sleep(backoff + jitter)
raise RuntimeError("Exceeded retry budget for 429 concurrency responses")
```
### JavaScript / TypeScript
```javascript
async function requestWithBackoff(fn, maxRetries = 5) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fn();
if (response.status !== 429) {
return response;
}
const baseMs = 200;
const backoffMs = baseMs * Math.pow(2, attempt);
const jitterMs = Math.floor(Math.random() * 100);
await new Promise((resolve) => setTimeout(resolve, backoffMs + jitterMs));
}
throw new Error("Exceeded retry budget for 429 concurrency responses");
}
```
### Go
```go
package main
import (
"errors"
"math/rand"
"net/http"
"time"
)
func requestWithBackoff(client *http.Client, req *http.Request, maxRetries int) (*http.Response, error) {
for attempt := 0; attempt <= maxRetries; attempt++ {
resp, err := client.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusTooManyRequests {
return resp, nil
}
backoff := 200 * time.Millisecond * time.Duration(1< $status, 'body' => $body];
}
$backoffMs = 200 * (2 ** $attempt);
$jitterMs = random_int(0, 100);
usleep(($backoffMs + $jitterMs) * 1000);
}
curl_close($ch);
throw new RuntimeException('Exceeded retry budget for 429 concurrency responses');
}
```
### Java
```java title="RequestWithBackoff.java"
import com.marketdata.sdk.exception.RateLimitError;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Supplier;
// The SDK already retries with exponential backoff internally. For a 429 caused
// by too many *concurrent* requests, wrap your call in an outer backoff and
// reduce fan-out. RateLimitError is the SDK's 429; honour getRetryAfter() if set.
static T requestWithBackoff(Supplier call, int maxRetries) throws InterruptedException {
for (int attempt = 0; ; attempt++) {
try {
return call.get();
} catch (RateLimitError e) {
if (attempt >= maxRetries) throw e;
long backoffMs = (long) (200 * Math.pow(2, attempt))
+ ThreadLocalRandom.current().nextInt(100);
Thread.sleep(e.getRetryAfter().map(d -> d.toMillis()).orElse(backoffMs));
}
}
}
```
### Kotlin
```kotlin title="RequestWithBackoff.kt"
import com.marketdata.sdk.exception.RateLimitError
import kotlin.random.Random
// The SDK already retries internally. For a 429 from too many *concurrent*
// requests, add outer backoff and reduce fan-out.
fun requestWithBackoff(maxRetries: Int = 5, call: () -> T): T {
var attempt = 0
while (true) {
try {
return call()
} catch (e: RateLimitError) {
if (attempt >= maxRetries) throw e
val backoffMs = (200.0 * Math.pow(2.0, attempt.toDouble())).toLong() + Random.nextInt(100)
Thread.sleep(e.retryAfter.map { it.toMillis() }.orElse(backoffMs))
attempt++
}
}
}
```
### C#
```csharp title="RequestWithBackoff.cs"
using MarketDataApp.Exceptions;
// The SDK already retries with exponential backoff internally, and its
// MaxConcurrentRequests option (default 50) bounds fan-out. For a 429 caused by
// too many *concurrent* requests, wrap your call in an outer backoff and lower
// the fan-out. RateLimitException is the SDK's 429; honour RetryAfter if set.
static async Task RequestWithBackoffAsync(Func> call, int maxRetries = 3)
{
for (var attempt = 0; ; attempt++)
{
try
{
return await call();
}
catch (RateLimitException e) when (attempt < maxRetries)
{
var backoff = TimeSpan.FromMilliseconds(200 * Math.Pow(2, attempt) + Random.Shared.Next(100));
await Task.Delay(e.RetryAfter ?? backoff);
}
}
}
```
## Common Concurrency Traps
1. Launching many jobs at once without queueing
2. Multiple app instances each using high internal concurrency
3. Immediate retries without backoff
4. Long request timeouts that keep too many requests in-flight
## When to Contact Support
If concurrency 429 persists after reducing fan-out and applying backoff, contact support with:
1. Approximate in-flight concurrency at failure time
2. Retry policy and worker pool settings
3. Timestamped request logs around 429 bursts
4. Whether you are using official SDKs or a custom client
---
# 400: URL Anatomy (Path vs Query Parameters)
Source: https://www.marketdata.app/docs/api/troubleshooting/url-parameters/index.md
## What This Guide Is For
This is a detailed guide for `400 BAD REQUEST` errors caused by incorrect URL construction.
If you are troubleshooting a `400 BAD REQUEST`, use the dedicated guide:
- [400: Bad Request](https://www.marketdata.app/docs/api/troubleshooting/bad-request)
## Introduction to URL Parameters
URL parameters (query strings) pass optional input to an endpoint after the path.
### URL Anatomy: Path vs Query String
A URL with parameters has the following structure:
- **Protocol**: `https://`
- **Host**: `api.marketdata.app`
- **Path (or Endpoint)**: `/v1/stocks/quotes/SPY/`
- **Query String**: Begins with a `?` and includes `token=token1234&dateformat=timestamp`
- `token` and `dateformat` are the names of the parameters.
- `token1234` and `timestamp` are the values assigned to those parameters.
- `&` is used to separate multiple parameters.
### What Goes In The Path
- Required route values like symbols, ids, or endpoint-specific path parts.
- Example: `/v1/stocks/quotes/SPY/`
### What Goes In The Query String
- Optional controls like `token`, `dateformat`, `limit`, and similar parameters.
- Query values always use `key=value` form.
- Multiple parameters are separated by `&`.
## How to Build URL Parameters
When building URL parameters, follow these guidelines to ensure they are structured correctly:
1. **Start with the Endpoint URL**: Identify the base URL of the API endpoint you are interacting with.
2. **Add a Question Mark**: Follow the base URL with a `?` to start the query string.
3. **Append Parameters**: Add parameters in the format `key=value`. Use `&` to separate multiple parameters.
4. **Encode Special Characters**: Use URL encoding to handle special characters in keys or values.
### Example
Suppose you want to request stock quotes for `SPY` with a specific date format and token authentication:
```
https://api.marketdata.app/v1/stocks/quotes/SPY/?token=token1234&dateformat=timestamp
```
### Common Mistakes
- **Incorrect Character Usage**: Ensure you use `?` to start and `&` to separate parameters.
- **Unencoded Characters**: Encode special characters like spaces (`%20`), plus (`%2B`), etc, using URL encoding.
---
# Universal Parameters
Source: https://www.marketdata.app/docs/api/universal-parameters/index.md
The following parameters are universal and can be used with any endpoint unless otherwise indicated in that endpoint's documentation.
---
# Columns
Source: https://www.marketdata.app/docs/api/universal-parameters/columns/index.md
The columns parameter is used to limit the results of any endpoint to only the columns you need.
## Parameter
## Use Example
```
https://api.marketdata.app/v1/stocks/quotes/AAPL/?columns=ask,bid
```
## Response Example
```json
{ "ask": [152.14], "bid": [152.12] }
```
## Values
### string
Use a list of columns names separated by commas to limit the response to just the columns requested.
> [!CAUTION]
>
> When using the columns parameter the `s` status output is suppressed from the response.
---
# Date Format
Source: https://www.marketdata.app/docs/api/universal-parameters/date-format/index.md
The dateformat parameter allows you to specify the format you wish to receive date and time information in. Regardless of format, all timestamps for US markets use the **America/New_York** timezone. See [Response Timezone](https://www.marketdata.app/docs/docs/api/dates-and-times#response-timezone) for full details on timezone handling.
## Parameter
dateformat=\
## Use Example
/candles/D/AAPL/?dateformat=timestamp
/candles/D/AAPL/?dateformat=unix
/candles/D/AAPL/?dateformat=spreadsheet
## Values
### timestamp
Receive dates and times as an ISO 8601 timestamp with timezone offset. All US market data is returned in America/New_York. For example, closing bell on Dec 30, 2020 for the NYSE would be: **2020-12-30 16:00:00 -05:00**.
### unix
Receive dates and times in unix format (seconds after the unix epoch). For example, closing bell on Dec 30, 2020 for the NYSE would be: **1609362000**. Unix timestamps are anchored to America/New_York — see [Response Timezone](https://www.marketdata.app/docs/docs/api/dates-and-times#response-timezone) for how to interpret them correctly.
### spreadsheet
Receive dates and times in spreadsheet format (days after the Excel epoch). For example, closing bell on Dec 30, 2020 for the NYSE would be: **44195.66667**. Spreadsheet format does not include a timezone offset. All times are expressed in America/New_York.
---
# Format
Source: https://www.marketdata.app/docs/api/universal-parameters/format/index.md
The format parameter is used to specify the format for your data. We support JSON and CSV formats. The default format is JSON.
## Parameter
format=\
## Use Example
/candles/daily/AAPL?format=json
/candles/daily/AAPL?format=csv
## Values
### json (default)
Use JSON to format the data in Javascript Object Notation (JSON) format. This format is ideal for programmatic use of the data.
### csv
Use CSV to format the data in lightweight comma separated value (CSV) format. This format is ideal for importing the data into spreadsheets.
---
# Headers Parameter
Source: https://www.marketdata.app/docs/api/universal-parameters/headers/index.md
The headers parameter is used to turn off headers when using CSV output.
## Parameter
headers=\
## Use Example
/candles/daily/AAPL?headers=false&format=csv
## Values
### true (default)
If the headers argument is not used, by default headers are turned on.
### false
Turns headers off and returns just the data points.
---
# Human Readable
Source: https://www.marketdata.app/docs/api/universal-parameters/human-readable/index.md
The human parameter will use human-readable attribute names in the JSON or CSV output instead of the standard camelCase attribute names. Use of this parameter will result in API output that can be loaded directly into a table or viewer and presented to an end-user with no further transformation required on the front-end.
## Parameter
human=\
## Use Example
https://api.marketdata.app/v1/stocks/quotes/AAPL/?human=true
## Response Example
```json
{
"Symbol": ["AAPL"],
"Ask": [152.63],
"Ask Size": [400],
"Bid": [152.61],
"Bid Size": [600],
"Mid": [152.62],
"Last": [152.63],
"Volume": [35021819],
"Date": [1668531422]
}
```
## Values
### True
The API will output human-readable attribute names instead of the standard camelCase attribute names. The output will be capitalized as a title, with the first letter of each major word capitalized. The `s` status response is also surpressed.
### False (default)
Output of attribute names will be according to API specifications using camelCase. If the `human` attribute is omitted, the default behavior is `false`.
---
# Limit
Source: https://www.marketdata.app/docs/api/universal-parameters/limit/index.md
The `limit` parameter allows you to limit the number of results for a particular API call or override an endpoint’s default limits to get more data.
- Default Limit: 10,000
- Maximum Limit: 50,000
In the example below, the daily candle endpoint by default returns the last 252 daily bars. By using limit you could modify the behavior return the last two weeks or 10 years of data.
## Parameter
limit=\
## Use Example
/candles/daily/AAPL?limit=10
/candles/daily/AAPL?limit=2520
## Values
### integer (required)
The limit parameter accepts any positive integer as an input.
---
# Data Mode
Source: https://www.marketdata.app/docs/api/universal-parameters/mode/index.md
The `mode` parameter allows you to control **how an API request is fulfilled**, including data freshness guarantees and credit usage. The `mode` parameter determines the response behavior used to fulfill each request.
Our API supports four freshness modes — `live`, `delayed`, `eod`, and `historical` — plus `cached`, which serves recently stored data at a reduced credit cost. These options balance immediacy, availability, and cost efficiency. Below is a detailed overview of each mode, including examples and recommended use-cases.
> [!NOTE]
> **[Premium Parameter]**
>
> This parameter is available only on paid plans. Free and trial plans cannot change the data mode. They receive historical data — the last fully-closed session — and a request for any fresher mode (`mode=live`, `mode=delayed`, or `mode=eod`) returns [402 Payment Required](https://www.marketdata.app/docs/api/troubleshooting/payment-required). See [Data Freshness](https://www.marketdata.app/docs/docs/account/data-freshness/) for what each plan receives.
## Live Mode
The `live` mode returns real-time data, providing the most current market information available. This mode is intended for time-sensitive use cases that require up-to-the-second pricing.
> [!NOTE]
> `mode=live` returns the freshest data your entitlements allow. It does not shorten an exchange-imposed delay. See [Data Freshness](https://www.marketdata.app/docs/docs/account/data-freshness/) for the freshness of each endpoint.
### Pricing for Live Mode
- Quotes: **1 credit per symbol** included in the response that has quote data (bid/ask/mid/last).
- Candles: **1 credit per 1,000 candles** included in the response.
- Bulk Candles: **1 credit per symbol*** included in the response.
- Other Endpoints: **1 credit per response**.
### Requesting Live Data
To request real-time data, append `mode=live` to your API call or omit the parameter entirely. If the `mode` parameter is not specified, `live` mode is used by default for paid accounts.
```http
GET https://api.marketdata.app/v1/options/chain/SPY/?mode=live
GET https://api.marketdata.app/v1/options/chain/SPY/
````
Both requests are valid and return the most recent data available for the specified symbol.
## Cached Mode
The `cached` mode returns recently stored data that may be a few seconds to several minutes old. This mode is designed to reduce credit usage when retrieving large amounts of quote data.
Data freshness is not guaranteed. Symbols with higher usage across Market Data customers tend to be refreshed more frequently.
### Pricing for Cached Mode
* Quotes: **1 credit per request**, regardless of the number of symbols.
* Historical Quotes: Unavailable
* Candles: Unavailable
* Bulk Candles: Unavailable
* Other Endpoints: Unavailable
This makes cached mode particularly cost-effective for bulk quote retrieval using endpoints such as [Option Chain](https://www.marketdata.app/docs/api/options/chain) and [Bulk Stock Quotes](https://www.marketdata.app/docs/api/stocks/quotes).
### Requesting Cached Data
```http
GET https://api.marketdata.app/v1/options/chain/SPY/?mode=cached
```
This request attempts to fulfill the response from Market Data’s cache. If cached data is available, the response is returned at a reduced credit cost.
### Using `maxage` with Cached Mode
The `maxage` parameter can be combined with `mode=cached` to control how old cached data may be before it is considered unusable.
#### How `maxage` Works
* **Purpose**: Sets a maximum acceptable age for cached data
* **Behavior**: If no cached data exists within the specified age window, the API returns an empty response with no credit charge
* **Format**: Accepts absolute datetimes or relative durations (e.g. `1h`, `5min`)
#### Example Scenarios
Assume the most recent cached options chain for AAPL is 3 minutes old.
**Scenario 1: Data within `maxage` limit**
```http
GET https://api.marketdata.app/v1/options/chain/AAPL/?mode=cached&maxage=5min
```
* **Result**: `203` response with data (1 credit charged)
* **Reason**: Cached data is newer than 5 minutes
**Scenario 2: Data exceeds `maxage` limit**
```http
GET https://api.marketdata.app/v1/options/chain/AAPL/?mode=cached&maxage=1min
```
* **Result**: `204` empty response (0 credits charged)
* **Reason**: Cached data exceeds the specified age limit
#### Strategic Usage
Using `maxage` enables efficient fallback logic:
1. First attempt to retrieve cached data with a defined freshness threshold
2. If a `204` response is returned, selectively request live data for required symbols
3. This maximizes cost efficiency while preserving data freshness where it matters
### Cached Mode Response Codes
When `mode=cached` is used, successful responses do not return `200 OK`. Instead:
* `203 NON-AUTHORITATIVE INFORMATION` – Request succeeded and was fulfilled from cache
* `204 NO CONTENT` – No cached data available within constraints; no credits charged
`204` is the deterministic cache-miss signal — it can only be returned by `mode=cached` (or `mode=cache` / `mode=stale`). For the full mapping of status codes across all modes, see [Status Codes](#status-codes) below.
## Delayed Mode
The `delayed` mode returns data delayed by **at least 15 minutes**. It is available on paid plans (Starter and up), which may request delayed data explicitly.
For when delayed data crosses into "historical" (a fully-closed prior session) — and why that happens at different times for stocks (4:15 PM ET) vs options (9:30 AM ET the next trading day) — see [Data Freshness](https://www.marketdata.app/docs/docs/account/data-freshness).
### Pricing for Delayed Mode
* Pricing is identical to live mode.
### Requesting Delayed Data
```http
GET https://api.marketdata.app/v1/options/chain/SPY/?mode=delayed
```
This request returns market data that is delayed by a minimum of 15 minutes.
## EOD and Historical Modes
Two further modes return a closed session rather than a delay on the current one.
* `mode=eod` returns the most recent session whose 4:15 PM ET cutoff has passed. Requires a paid plan.
* `mode=historical` returns the most recent fully-closed session — the API waits for the next session to open before it counts a session as closed. This is what free and trial accounts receive, and the only mode they may request explicitly.
### Pricing for EOD and Historical Modes
* Pricing is identical to live mode.
### Requesting EOD or Historical Data
```http
GET https://api.marketdata.app/v1/options/chain/SPY/?mode=eod
GET https://api.marketdata.app/v1/options/chain/SPY/?mode=historical
```
## Mode Comparison
| Feature | Live Mode | Cached Mode | Delayed Mode | EOD Mode | Historical Mode |
|--------------------------------|--------------------------------------|----------------------------|---------------------------------|------------------------------|---------------------------|
| **Data Timeliness** | Real-time | Seconds to minutes old | ≥ 15 minutes delayed | Last session past 4:15 PM ET | Last fully-closed session |
| **Pricing** | 1 credit per symbol with quote data | 1 credit per request | Same as live | Same as live | Same as live |
| **Ideal Use-Case** | Time-sensitive decisions | Bulk quote retrieval | Non-time-sensitive applications | End-of-day reporting | Backtesting and research |
| **Default Behavior** | Paid accounts (if `mode` is omitted) | Must specify `mode=cached` | Must specify `mode=delayed` | Must specify `mode=eod` | Free & trial accounts |
| **Paid Accounts Access** | ✅ | ✅ | ✅ | ✅ | ✅ |
| **Free/Trial Accounts Access** | ❌ | ❌ | ❌ | ❌ | ✅ |
Free and trial accounts receive historical data on every request. See [Data Freshness](https://www.marketdata.app/docs/docs/account/data-freshness/) for the freshness of each endpoint on each plan.
* Choose **`mode=live`** when immediate data freshness is required.
* Use **`mode=cached`** to reduce credit usage when working with large symbol sets.
* Select **`mode=delayed`** for applications where timing precision is not critical.
* Select **`mode=eod`** or **`mode=historical`** when a closed session is what you want.
## Status Codes
Market Data uses HTTP status codes to communicate where a successful response came from. **All three of `200`, `203`, and `204` are success responses — your client must accept all of them.**
| Status | Meaning | When it occurs |
|-------------------------------------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
| `200 OK` | Response was freshly fetched from the upstream provider. | Any mode, when no cache layer can satisfy the request. |
| `203 Non-Authoritative Information` | Response was served from a cache layer (Redis, database quote cache, response log, option-chain cache, etc.). | Any mode. Common during market hours regardless of `mode=live`, `mode=delayed`, or no `mode` specified. The body is identical in shape to a `200`. |
| `204 No Content` | No cached data is available within the requested constraints. No credits charged. | Only when `mode=cached` (also `mode=cache` / `mode=stale`). Never returned by other modes. |
> [!CAUTION]
> **[Mode does not deterministically map to status code]**
>
> A common (incorrect) assumption is that `mode=live` always returns `200` and `mode=delayed` always returns `203`. Both can return either `200` or `203` depending on whether a cache layer can satisfy the request at the moment. Only `mode=cached` is deterministic — it returns `203` on cache hit or `204` on cache miss, never `200`.
### Handling `204` (cache miss on `mode=cached`)
When `mode=cached` returns `204`, the typical pattern is to fall back to a live request. A short Python example:
```python
import requests
def get_option_chain(token):
url = "https://api.marketdata.app/v1/options/chain/AAPL/"
headers = {"Authorization": f"Bearer {token}"}
r = requests.get(url, params={"mode": "cached"}, headers=headers)
if r.status_code in (200, 203):
return r.json()
if r.status_code == 204:
# Cache miss — fall back to live (incurs normal live-mode credit cost)
r = requests.get(url, params={"mode": "live"}, headers=headers)
if r.status_code in (200, 203):
return r.json()
r.raise_for_status()
```
A few notes on the retry pattern:
- A retry with `mode=live` is billed at the full live-mode credit cost — see [Pricing for Live Mode](#pricing-for-live-mode). The original `204` response is free.
- Cap the retry at one attempt. A subsequent `204` should not occur on `mode=live`, but a circuit breaker is still wise.
- If your plan does not include `mode=cached` access (Free/Trial), `mode=cached` requests return `402 Payment Required` rather than `204`. See [402: Payment Required](https://www.marketdata.app/docs/api/troubleshooting/payment-required).
For the full list of HTTP status codes returned by the API (including `4xx` and `5xx`), see [Troubleshooting](https://www.marketdata.app/docs/api/troubleshooting).
---
# Offset
Source: https://www.marketdata.app/docs/api/universal-parameters/offset/index.md
The offset parameter is used together with limit to allow you to implement pagination in your application. Offset will allow you to return values starting at a certain value.
## Parameter
offset=\
## Use Example
/candles/daily/AAPL?limit=10&offset=10
## Values
### integer (required)
The limit parameter accepts any positive integer as an input.
---
# Token
Source: https://www.marketdata.app/docs/api/universal-parameters/token/index.md
The token parameter allows you to submit a read-only access token as a parameter.
> [!CAUTION]
> **[Security Warning]**
>
> When submitting your token in a URL, your token is exposed in server logs, cached in your browser, or otherwise made available. We do not recommend using your token as a parameter. This should only be used as a last resort in when you are unable to submit your token in a header.
## Parameter
token=\
## Use Example
https://api.marketdata.app/v1/stocks/quotes/SPY/?token=put-your-token-here
## Values
### Token
Submit your read-only access token as a value.
---
# Utilities
Source: https://www.marketdata.app/docs/api/utilities/index.md
These endpoints are designed to assist with API-related service issues, including checking the online status and uptime.
## Root Endpoint For Utilities
```
https://api.marketdata.app/
```
## Utilities Endpoints
---
# Request Headers
Source: https://www.marketdata.app/docs/api/utilities/headers/index.md
This endpoint allows users to retrieve a JSON response of the headers their application is sending, aiding in troubleshooting authentication issues, particularly with the Authorization header.
> [!TIP]
> The values in sensitive headers such as `Authorization` are partially redacted in the response for security purposes.
## Endpoint
```
https://api.marketdata.app/headers/
```
### Method
```
GET
```
## Request Example
### HTTP
**GET** [https://api.marketdata.app/headers/](https://api.marketdata.app/headers/)
### JavaScript
```js title="headersCheck.js"
import { MarketDataClient } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const h = await client.utilities.headers();
console.log(h["user-agent"]);
console.log(h["accept-encoding"]);
} catch (error) {
console.error(error);
}
```
### TypeScript
```typescript title="headersCheck.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { HeadersResponse } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const h: HeadersResponse = await client.utilities.headers();
console.log(h["user-agent"]);
console.log(h["accept-encoding"]);
} catch (error) {
console.error(error);
}
```
### Python
```python title="headersCheck.py"
import requests
url = "https://api.marketdata.app/headers/"
response = requests.get(url)
print(response.text)
```
### PHP
```php title="headersCheck.php"
use MarketDataApp\Client;
$client = new Client();
$headers = $client->utilities->headers();
// Display all headers
echo $headers;
```
### Java
```java title="HeadersCheck.java"
import com.marketdata.sdk.MarketDataClient;
public class HeadersCheck {
public static void main(String[] args) {
try (MarketDataClient client = new MarketDataClient()) {
System.out.println(client.utilities().headers().values());
}
}
}
```
### Kotlin
```kotlin title="HeadersCheck.kt"
import com.marketdata.sdk.MarketDataClient
fun main() {
MarketDataClient().use { client ->
println(client.utilities().headers().values())
}
}
```
### C#
```csharp title="HeadersCheck.cs"
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
foreach (var (name, value) in (await client.Utilities.GetHeadersAsync()).Values)
{
Console.WriteLine($"{name}: {value}");
}
```
## Response Example
```json
{
"accept": "*/*",
"accept-encoding": "gzip",
"authorization": "Bearer *******************************************************YKT0",
"cache-control": "no-cache",
"cf-connecting-ip": "132.43.100.7",
"cf-ipcountry": "US",
"cf-ray": "85bc0c2bef389lo9",
"cf-visitor": "{\"scheme\":\"https\"}",
"connection": "Keep-Alive",
"host": "api.marketdata.app",
"postman-token": "09efc901-97q5-46h0-930a-7618d910b9f8",
"user-agent": "PostmanRuntime/7.36.3",
"x-forwarded-proto": "https",
"x-real-ip": "53.43.221.49"
}
```
## Response Attributes
### Success
- **Headers** `object`
A JSON object representing the headers received from the user's request. This object includes standard and custom headers along with their respective values.
### Error
- **s** `string`
Status will be `error` if the request produces an error response.
- **errmsg** `string`
An error message.
This endpoint is particularly useful for debugging issues related to authentication by allowing users to see exactly what headers are being sent to the API.
---
# API Status
Source: https://www.marketdata.app/docs/api/utilities/status/index.md
Check the current status of Market Data services and historical uptime. The status of the Market Data API is updated every 5 minutes. Historical uptime is available for the last 30 and 90 days.
> [!TIP]
> This endpoint will continue to respond with the current status of the Market Data API, even if the API is offline. This endpoint is public and does not require a token. This endpoint is also unversioned: it is served at `/status/`, not under `/v1/` like every other endpoint.
## Endpoint
```
https://api.marketdata.app/status/
```
### Method
```
GET
```
## Request Example
### HTTP
**GET** [https://api.marketdata.app/status/](https://api.marketdata.app/status/)
### JavaScript
```js title="statusCheck.js"
import { MarketDataClient } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const s = await client.utilities.status();
for (let i = 0; i < s.service.length; i++) {
console.log(`${s.service[i]}: ${s.status[i]} (online=${s.online[i]})`);
}
} catch (error) {
console.error(error);
}
```
### TypeScript
```typescript title="statusCheck.ts"
import { MarketDataClient } from "@marketdata/sdk";
import type { ApiStatusResponse } from "@marketdata/sdk";
const client = new MarketDataClient();
try {
const s: ApiStatusResponse = await client.utilities.status();
for (let i = 0; i < s.service.length; i++) {
console.log(`${s.service[i]}: ${s.status[i]} (online=${s.online[i]})`);
}
} catch (error) {
console.error(error);
}
```
### Python
```python title="statusCheck.py"
import requests
url = "https://api.marketdata.app/status/"
response = requests.get(url)
print(response.text)
```
### PHP
```php title="statusCheck.php"
use MarketDataApp\Client;
$client = new Client();
$status = $client->utilities->api_status();
// Display API status for all services
echo $status;
```
### Java
```java title="StatusCheck.java"
import com.marketdata.sdk.MarketDataClient;
public class StatusCheck {
public static void main(String[] args) {
try (MarketDataClient client = new MarketDataClient()) {
client.utilities().status().values().forEach(System.out::println);
}
}
}
```
### Kotlin
```kotlin title="StatusCheck.kt"
import com.marketdata.sdk.MarketDataClient
fun main() {
MarketDataClient().use { client ->
client.utilities().status().values().forEach(::println)
}
}
```
### C#
```csharp title="StatusCheck.cs"
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
foreach (var service in (await client.Utilities.GetStatusAsync()).Values)
{
Console.WriteLine(service);
}
```
## Response Example
```json
{
"s": "ok",
"service": [
"/v1/funds/candles/",
"/v1/markets/status/",
"/v1/options/chain/",
"/v1/options/expirations/",
"/v1/options/lookup/",
"/v1/options/quotes/",
"/v1/stocks/bulkcandles/",
"/v1/stocks/bulkquotes/",
"/v1/stocks/candles/",
"/v1/stocks/earnings/",
"/v1/stocks/news/",
"/v1/stocks/quotes/"
],
"status": [
"online",
"online",
"online",
"online",
"online",
"online",
"online",
"online",
"online",
"online",
"online",
"online",
"online",
"online"
],
"online": [true, true, true, true, true, true, true, true, true, true, true, true, true, true],
"uptimePct30d": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
"uptimePct90d": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
"updated": [1734036832, 1734036832, 1734036832, 1734036832, 1734036832, 1734036832, 1734036832, 1734036832, 1734036832, 1734036832, 1734036832, 1734036832, 1734036832, 1734036832]
}
```
## Response Attributes
### Success
- **s** `string`
Will always be `ok` when the status information is successfully retrieved.
- **service** `array[string]`
The list of services being monitored.
- **status** `array[string]`
The current status of each service (`online` or `offline`).
- **online** `array[boolean]`
Boolean indicators for the online status of each service.
- **uptimePct30d** `array[number]`
The uptime percentage of each service over the last 30 days.
- **uptimePct90d** `array[number]`
The uptime percentage of each service over the last 90 days.
- **updated** `array[date]`
The timestamp of the last update for each service's status.
### Error
- **s** `string`
Status will be `error` if the request produces an error response.
- **errmsg** `string`
An error message.
For more details on the API's status, visit the [Market Data API Status Page](https://www.marketdata.app/status/).
---
# Market Data SDKs
Source: https://www.marketdata.app/docs/sdk/index.md
Welcome to the SDK documentation for Market Data. Our SDKs are designed to help you integrate our financial data into your applications seamlessly. Whether you're building a financial analysis tool, a stock market tracker, or any application that requires real-time or historical financial data, our SDKs are here to make your development process easier.
## Available SDKs
Our SDKs are available for a multitude of programming languages and platforms, serving a diverse array of developers and their projects:
[](https://www.marketdata.app/docs/sdk/postman)
### [Postman Collection](https://www.marketdata.app/docs/sdk/postman)
[Comprehensive Postman Collection for easy API integration and testing.](https://www.marketdata.app/docs/sdk/postman)
[](https://www.marketdata.app/docs/sdk/py)
### [Python SDK](https://www.marketdata.app/docs/sdk/py)
[Perfect for data analysis, backend services, AI, ML, and automation scripts.](https://www.marketdata.app/docs/sdk/py)
[](https://www.marketdata.app/docs/sdk/js)
### [JavaScript / TypeScript SDK](https://www.marketdata.app/docs/sdk/js)
[Type-safe Market Data integration for Node.js services and TypeScript applications.](https://www.marketdata.app/docs/sdk/js)
[](https://www.marketdata.app/docs/sdk/java)
### [Java / Kotlin SDK](https://www.marketdata.app/docs/sdk/java)
[Typed Market Data integration for JVM applications, with first-class Kotlin interop.](https://www.marketdata.app/docs/sdk/java)
[](https://www.marketdata.app/docs/sdk/csharp)
### [C# / .NET SDK](https://www.marketdata.app/docs/sdk/csharp)
[Async-first, typed Market Data integration for .NET services, ASP.NET Core, and desktop apps.](https://www.marketdata.app/docs/sdk/csharp)
[](https://www.marketdata.app/docs/sdk/go)
### [Go SDK](https://www.marketdata.app/docs/sdk/go)
[High performance Market Data SDK integration for enterprise-level backend systems.](https://www.marketdata.app/docs/sdk/go)
[](https://www.marketdata.app/docs/sdk/php)
### [PHP SDK](https://www.marketdata.app/docs/sdk/php)
[Market Data integration for web applications and server-side processing.](https://www.marketdata.app/docs/sdk/php)
Every SDK is crafted for ease of use, guaranteeing a straightforward setup process with minimal configuration required.
## Getting Started
To start using our SDKs, select the SDK for your preferred language or platform from the sidebar. Each SDK section includes a comprehensive guide on setting up, making requests, and handling responses.
## Support
If you encounter any issues or have questions regarding our SDKs, please refer to the FAQ section or contact our support team through the [support portal](https://www.marketdata.app/dashboard/).
Thank you for choosing Market Data for your financial data needs. We're excited to see what you build with our SDKs!
---
# C# / .NET SDK
Source: https://www.marketdata.app/docs/sdk/csharp/index.md
Welcome to the Market Data C#/.NET SDK documentation. This SDK lets you integrate Market Data services into any .NET application. It ships typed, records-based responses, an async-first API where every endpoint returns a `Task` and accepts a `CancellationToken`, automatic retries and rate-limit tracking, and a focused exception hierarchy you can branch on.
The SDK multi-targets **.NET 8 and .NET 10** (both LTS), integrates with `Microsoft.Extensions.Configuration`, `Microsoft.Extensions.Logging`, and dependency injection out of the box, and works equally well from a console app, a background service, or ASP.NET Core.
## Quick Start
```csharp
using MarketDataApp;
// With no HttpClient supplied, the SDK creates and owns one configured to its requirements.
// It reads MARKETDATA_TOKEN from the environment (or a .env file), validates the token on
// startup, and seeds the rate-limit snapshot before your first request.
using var client = await MarketDataClient.CreateAsync();
// A single quote — GetQuoteAsync returns a list; a single symbol is row 0.
var quote = (await client.Stocks.GetQuoteAsync("AAPL")).Values[0];
Console.WriteLine(quote); // AAPL mid=311.94 last=311.92
```
## Open Source
The SDK is open source and available on GitHub. Feel free to contribute to the project, report bugs, or request new features.
- [C#/.NET SDK on GitHub](https://github.com/MarketDataApp/sdk-csharp/)
## Documentation
The best source for documentation on the SDK is right here. This documentation is the most up-to-date and accurate source of information on the SDK.
## Using the SDK
This SDK is designed to help you get up and running with Market Data's APIs as quickly as possible, providing all the tools you need to access real-time stock and options prices, historical data, and much more.
### Getting Started
1. [Install the SDK](https://www.marketdata.app/docs/sdk/csharp/installation) from NuGet.
2. Set up your [authentication token](https://www.marketdata.app/docs/sdk/csharp/authentication) to access the API.
3. Learn about the [client](https://www.marketdata.app/docs/sdk/csharp/client) and how to make your first API requests.
4. Configure [Settings](https://www.marketdata.app/docs/sdk/csharp/settings) to customize output format, date format, and other universal parameters.
5. Explore the available endpoints for [stocks](https://www.marketdata.app/docs/sdk/csharp/stocks), [options](https://www.marketdata.app/docs/sdk/csharp/options), [funds](https://www.marketdata.app/docs/sdk/csharp/funds), [markets](https://www.marketdata.app/docs/sdk/csharp/markets), and [utilities](https://www.marketdata.app/docs/sdk/csharp/utilities).
### Support
- If you have any questions or need further assistance, please don't hesitate to open a ticket at our [helpdesk](https://www.marketdata.app/dashboard/).
- If you find a bug you may also [open an issue in our GitHub repository](https://github.com/MarketDataApp/sdk-csharp/issues). Please only open issues if you find a bug. Use our helpdesk for general questions or implementation assistance.
---
# Authentication (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/authentication/index.md
The Market Data API uses a **Bearer Token** for authentication. The token is required for almost every request. Your token should have been e-mailed to you when you first signed up for an account. If you do not have a token or have lost your sign-up email, request a new token from the [Market Data Dashboard](https://www.marketdata.app/dashboard/).
There are four ways to set your token when using the C#/.NET SDK, in increasing order of precedence:
1. Store it in [.NET user secrets](https://learn.microsoft.com/aspnet/core/security/app-secrets) _(recommended for local development)_
2. Load it from a `.env` file in the working directory
3. Set it from an environment variable _(recommended for production; highest precedence of the automatic sources)_
4. Pass it directly through `MarketDataClientOptions` when creating the [client](https://www.marketdata.app/docs/sdk/csharp/client)
When you create a client without explicit options, the SDK loads `MARKETDATA_TOKEN` (and every other `MARKETDATA_*` setting) from user secrets, then the `.env` file, then environment variables — later sources override earlier ones. Explicit options bypass that cascade entirely.
> [!TIP]
> When your code is running in a production environment, we recommend using an environment variable or a managed secret store to ensure your token is not stored with your code. This is the most secure way to set your token.
## How To Set Up The Environment Variable
### Set The Environment Variable In The Console
### Mac / Linux
This command sets the environment variable for the current session only. If you open a new terminal or restart your computer, it will not persist.
```bash
export MARKETDATA_TOKEN="your_api_token"
```
#### Make The Variable Persistent
Add the `export` line to your shell's profile script (`~/.zshrc`, `~/.bashrc`, `~/.bash_profile`, etc.), then restart your terminal or run `source ~/.zshrc` (adjusting for your shell).
### Windows
`setx` sets the variable permanently, but it is not available in the current terminal session — open a new one after running it.
```powershell
setx MARKETDATA_TOKEN "your_api_token"
```
### Using .NET User Secrets
For local development, user secrets keep the token out of your project directory entirely. From your executable project's directory:
```bash
dotnet user-secrets init
dotnet user-secrets set "MARKETDATA_TOKEN" "your_api_token"
```
The SDK reads user secrets automatically when you create a client without explicit options.
### Using a .env File
The SDK also loads a `.env` file from your working directory at startup. Create a file named `.env` in your project root:
```ini title=".env"
MARKETDATA_TOKEN=your_api_token
```
> [!WARNING]
> Add `.env` to your `.gitignore` so the token is not committed to source control.
### Make A Test Request
Verify your authentication is working by making a test request against `SPY` (or any symbol that requires authentication). Do **not** use `AAPL` to test authentication — `AAPL` is a free test symbol and returns data even when you are not authenticated.
```csharp
using MarketDataApp;
using MarketDataApp.Exceptions;
try
{
// No need to pass a token here — the SDK reads MARKETDATA_TOKEN automatically,
// and CreateAsync validates it against the API before returning the client.
using var client = await MarketDataClient.CreateAsync();
var quote = (await client.Stocks.GetQuoteAsync("SPY")).Values[0];
Console.WriteLine(quote); // SPY mid=642.18 last=642.05
}
catch (AuthenticationException e)
{
Console.WriteLine($"Authentication failed: {e.Message}");
}
```
## Passing the Token Directly
If you prefer to pass the token explicitly (not recommended for production code), set `ApiToken` on `MarketDataClientOptions`. Explicit options replace the automatic cascade, so nothing is read from the environment:
```csharp
using MarketDataApp;
var options = new MarketDataClientOptions
{
ApiToken = Environment.GetEnvironmentVariable("MY_APP_MARKETDATA_TOKEN")
};
using var client = await MarketDataClient.CreateAsync(options);
```
## Startup Token Validation
When a token is configured, the client validates it at startup by default — on **both** construction paths. The startup `GET /user/`:
1. fails fast on an invalid token by throwing `AuthenticationException`, and
2. seeds the client-wide rate-limit snapshot (`client.LatestRateLimit`) from the `x-api-ratelimit-*` response headers before your first data request.
In asynchronous applications, prefer the async factory, which runs the validation without blocking the calling thread:
```csharp
using var client = await MarketDataClient.CreateAsync(options);
```
The plain constructor runs the same validation as a blocking request, which makes it the fail-fast choice for synchronous hosts and dependency-injection factories that cannot `await`:
```csharp
// Validates the token with a blocking GET /user/ when a token is configured.
using var client = new MarketDataClient(options);
```
Startup validation is governed by `MarketDataClientOptions.ValidateTokenOnStartup`. Set it to `false` for first-request (lazy) validation with no startup network I/O:
```csharp
var options = new MarketDataClientOptions { ValidateTokenOnStartup = false };
```
> [!NOTE]
> **[Demo mode]**
>
> If no token is found anywhere in the cascade, the SDK runs in **demo mode** — startup validation is skipped and you can call the free, public endpoints (such as `AAPL` quotes and `client.Utilities.GetStatusAsync()`). Authenticated endpoints throw `AuthenticationException` on first use.
## Next Steps
After successful authentication, read the overview of how the [client](https://www.marketdata.app/docs/sdk/csharp/client) works, then configure [Settings](https://www.marketdata.app/docs/sdk/csharp/settings) to customize output format, date format, and other universal parameters.
---
# Client (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/client/index.md
The `MarketDataClient` is the entry point for the SDK. It groups the API into five resources — `Stocks`, `Options`, `Funds`, `Markets`, and `Utilities` — and owns the cross-cutting behavior: configuration loading, authentication, retries, timeouts, and rate-limit tracking.
## Get Started Quickly
```csharp
using MarketDataApp;
// Reads MARKETDATA_* from the environment, validates the token, seeds the rate limits.
using var client = await MarketDataClient.CreateAsync();
var quote = (await client.Stocks.GetQuoteAsync("AAPL")).Values[0];
Console.WriteLine(quote); // AAPL mid=311.94 last=311.92
```
## MarketDataClient
### Two ways to construct
Every entry point exists in two flavors: an **async factory** (`CreateAsync`) and a **constructor**. Both perform the same startup token validation when a token is configured; the difference is only whether that validation blocks the calling thread.
| Entry point | Startup validation | Use when |
|-------------------------------------------|--------------------|---------------------------------------------------------------------------|
| `await MarketDataClient.CreateAsync(...)` | Asynchronous | Anywhere you can `await` — the recommended default. |
| `new MarketDataClient(...)` | Blocking | Synchronous hosts and dependency-injection factories that cannot `await`. |
Both accept an optional `MarketDataClientOptions`. When omitted, options are loaded from the [configuration cascade](https://www.marketdata.app/docs/sdk/csharp/settings#configuration-cascade) (user secrets, `.env`, environment variables).
### Two ways to own the HttpClient
The SDK never disposes an `HttpClient` it did not create, and never reconfigures one you hand it.
- **SDK-owned (default).** The overloads without an `HttpClient` parameter create one backed by `MarketDataClient.CreateDefaultHttpHandler()` (2-second connection timeout, pooled-connection rotation) with the `HttpClient`-level timeout disabled, because the SDK enforces its own fixed 99-second request timeout. `Dispose()` also disposes the owned client.
- **Application-owned.** Supply your own `HttpClient` — for example one from `IHttpClientFactory` — and the application controls its lifetime. Its handler and `Timeout` are respected as-is.
```csharp
// SDK-owned transport (simplest).
using var client = await MarketDataClient.CreateAsync(options);
// Application-owned transport. CreateDefaultHttpHandler() supplies the 2-second
// connect timeout; a plain new HttpClient() works too.
using var httpClient = new HttpClient(MarketDataClient.CreateDefaultHttpHandler());
var client2 = await MarketDataClient.CreateAsync(httpClient, options);
```
### Dependency injection (ASP.NET Core)
Register the client with one line. `AddMarketDataClient` lives in the `Microsoft.Extensions.DependencyInjection` namespace, so it is discoverable from `Program.cs` without an extra `using`:
```csharp
// Program.cs
builder.Services.AddMarketDataClient(builder.Configuration);
```
Then take `MarketDataClient` as a constructor-injected dependency or a minimal-API parameter:
```csharp
app.MapGet("/quote/{symbol}", async (string symbol, MarketDataClient client, CancellationToken ct) =>
Results.Ok((await client.Stocks.GetQuoteAsync(symbol, cancellationToken: ct)).Values));
```
`AddMarketDataClient` registers `MarketDataClient` as a **singleton** over an `IHttpClientFactory`-managed `HttpClient` backed by the SDK's default handler. Three overloads resolve the options for you:
| Overload | Options source |
|--------------------------------------------------------|-------------------------------------------------------------------------------------------|
| `AddMarketDataClient()` | `MarketDataClientOptions.FromEnvironment()` — user secrets, `.env`, environment variables |
| `AddMarketDataClient(IConfiguration configuration)` | `MarketDataClientOptions.FromConfiguration(configuration)` — the `MARKETDATA_*` keys |
| `AddMarketDataClient(MarketDataClientOptions options)` | the supplied instance |
Registrations use `TryAdd*`, so you can override either the options or the client by registering your own first. When your app has logging configured, the container's `ILogger` is auto-wired into the SDK. Because the DI path uses the constructor, a configured token is validated with a blocking `GET /user/` the first time the singleton is resolved — typically during startup — so an invalid token fails the application at startup rather than on the first request. Register options with `ValidateTokenOnStartup = false` to defer that.
## Async and Cancellation
Every endpoint method is asynchronous, returns a `Task`, and accepts an optional `CancellationToken` as its last parameter. There are no synchronous endpoint methods.
```csharp
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));
var quotes = await client.Stocks.GetQuotesAsync(
["AAPL", "MSFT", "GOOG"],
cancellationToken: timeout.Token);
```
Caller cancellation surfaces as `OperationCanceledException`, which is deliberately **not** part of the SDK's exception hierarchy — it means *you* stopped the request. An SDK timeout surfaces as `NetworkException`.
### Composing async calls
Endpoint calls are ordinary tasks, so they compose with `Task.WhenAll`. All requests share the client's retry, rate-limit, and concurrency behavior; `MaxConcurrentRequests` (default 50) bounds how many are in flight at once.
```csharp
var aaplTask = client.Stocks.GetQuoteAsync("AAPL");
var msftTask = client.Stocks.GetQuoteAsync("MSFT");
var statusTask = client.Markets.GetStatusAsync("US");
await Task.WhenAll(aaplTask, msftTask, statusTask);
var aapl = aaplTask.Result.Values[0]; // safe: the tasks are already complete
var msft = msftTask.Result.Values[0];
var status = statusTask.Result.Values[0];
```
## Scalar Parameters and Request Objects
Every endpoint has two overloads. The **scalar** overload takes the common parameters directly and is the fastest way to make a call:
```csharp
var candles = await client.Stocks.GetCandlesAsync(StockResolution.Daily, "AAPL", countback: 30);
```
The **request-object** overload takes an immutable request record. Required values are constructor arguments; optional values are `init` properties. Use it when several optional filters should be grouped or reused:
```csharp
var request = new StockCandlesRequest(StockResolution.Daily, "AAPL")
{
Countback = 30,
AdjustDividends = true
};
var candles = await client.Stocks.GetCandlesAsync(request);
```
Both overloads also accept an optional [`MarketDataRequestOptions`](https://www.marketdata.app/docs/sdk/csharp/settings#universal-parameters) for the universal parameters (date format, mode, columns, limit, offset).
## The Response Object
Every endpoint returns a typed response record derived from `MarketDataResponse`, wrapping the decoded data plus request metadata, with a uniform surface regardless of endpoint or format.
```csharp
public abstract record MarketDataResponse
{
public T Values { get; } // the typed payload (e.g. IReadOnlyList)
public int StatusCode { get; } // 200, 203, or 404
public bool IsNoData { get; } // true on a 404 "no_data" response
public string? RequestId { get; } // server request id, for support tickets
public Uri RequestUrl { get; } // the absolute request URL
public RateLimitSnapshot? RateLimit { get; } // this request's rate limit
public IReadOnlyList Parts { get; } // one per HTTP request
public bool IsComposite { get; } // assembled from several HTTP responses
public string RawBody { get; } // the raw response body, as sent
public bool IsJson { get; }
public bool IsCsv { get; }
public bool IsHtml { get; }
public string SaveToFile(string path); // write the body; returns the path
public Task SaveToFileAsync(string path, CancellationToken cancellationToken = default);
}
```
```csharp
var response = await client.Stocks.GetQuoteAsync("AAPL");
response.Values; // IReadOnlyList — the part you usually want
response.StatusCode; // 200
response.RequestId; // e.g. for a support ticket
response.RateLimit; // this request's rate-limit snapshot (may be null)
await response.SaveToFileAsync("aapl.json"); // cache the raw body
```
`SaveToFile` chooses the representation from the file extension (`.json`, `.csv`, `.html`) when the response can provide it, and falls back to the raw body otherwise.
## Error Handling
Everything the SDK throws is a `MarketDataException`. The seven subtypes below are the complete set. Each exception carries support context — `StatusCode`, `RequestId`, `RequestUrl`, `Timestamp`, and `ExceptionType` — plus a preformatted `SupportInfo` block ready to paste into a support ticket.
| Subtype | When it's thrown |
|---------------------------|--------------------------------------------------------------------------------------------------|
| `AuthenticationException` | Missing or invalid token (HTTP 401 / 403) |
| `BadRequestException` | Invalid parameters (HTTP 400) |
| `NotFoundException` | The resource doesn't exist (HTTP 404 that is not a "no data" response) |
| `RateLimitException` | Quota exceeded (HTTP 429), or the client-side rate-limit guard refused to send; see `RetryAfter` |
| `ServerException` | API-side failure (HTTP 5xx); see `RetryAfter` |
| `NetworkException` | Connection failure or timeout |
| `ParseException` | The response could not be decoded |
Request-shape mistakes caught **before** any HTTP call — an invalid date window, an unsupported option on a typed method — throw `ArgumentException`, the standard .NET signal for a bad argument.
```csharp
using MarketDataApp.Exceptions;
try
{
var quote = await client.Stocks.GetQuoteAsync("AAPL");
}
catch (AuthenticationException)
{
Console.WriteLine("Check your token");
}
catch (RateLimitException e)
{
var wait = e.RetryAfter is { } delay ? $"{delay.TotalSeconds:F0}s" : "a moment";
Console.WriteLine($"Rate limited — retry after {wait}");
}
catch (MarketDataException e)
{
// Any other case. Attach SupportInfo to a bug report.
Console.WriteLine(e.SupportInfo);
}
```
## Retries and Timeouts
Transient failures — `NetworkException` and HTTP 501–599 — are retried automatically up to `MaxRetries` times (default 3) with a fixed exponential backoff (1s, 2s, 4s, capped at 30s). A server-supplied `Retry-After` header is honored. Authentication, validation, parsing, and rate-limit failures are never retried; before retrying a server error the SDK also consults the cached `/status/` reading and skips the retry if the API reports itself offline.
Every HTTP attempt has a fixed **99-second request timeout**; SDK-owned and DI-registered transports add a **2-second connection timeout** through the default handler. Neither is configurable — `MaxRetries` is the only retry knob.
## Rate Limits
The API reports your quota in `x-api-ratelimit-*` response headers. The SDK captures them two ways:
- **Per response:** `response.RateLimit` — the snapshot returned with that request.
- **Client-wide:** `client.LatestRateLimit` — the most recent complete snapshot the client has seen, seeded by the startup `GET /user/` when a token is configured.
```csharp
if (client.LatestRateLimit is { } limit)
{
Console.WriteLine($"{limit.Remaining}/{limit.Limit} remaining, resets {limit.Reset:HH:mm}");
}
```
`RateLimitSnapshot` exposes `Limit`, `Remaining`, `Reset` (a `DateTimeOffset`), and `Consumed`. When the latest snapshot shows the quota exhausted and the reset time has not passed, the client refuses to send further requests and throws `RateLimitException` immediately, so you never burn calls that would fail anyway.
## Disposing the Client
`MarketDataClient` implements `IDisposable`. Disposing it releases its internal resources and — only when the SDK created the transport — the owned `HttpClient`. An `HttpClient` you supplied is never disposed. A single client instance is safe to use concurrently from multiple threads or requests, so the typical pattern is one long-lived client per application:
```csharp
using var client = await MarketDataClient.CreateAsync();
// ... use for the lifetime of the app
```
---
# Funds (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/funds/index.md
The C#/.NET SDK from Market Data provides methods to streamline your use of the Funds endpoints. These methods provide a typed interface over the underlying HTTP requests and responses; every method is asynchronous and accepts an optional `CancellationToken`.
Reach the resource through `client.Funds`. For CSV output, call the paired `*CsvAsync` method of the same name (for example `GetCandlesCsvAsync`).
## Funds Endpoints
---
# Fund Candles (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/funds/candles/index.md
Retrieve historical NAV candles (open/high/low/close) for a mutual fund symbol. Fund candles have no volume.
## Making Requests
Use `GetCandlesAsync` on the `Funds` resource, passing a `FundResolution` and a symbol.
```csharp
Task GetCandlesAsync(
FundResolution resolution, string symbol,
DateOnly? date = null, DateOnly? from = null, DateOnly? to = null, int? countback = null,
MarketDataRequestOptions? options = null, CancellationToken cancellationToken = default)
Task GetCandlesAsync(FundCandlesRequest request, ...)
```
### FundCandlesRequest
```csharp
new FundCandlesRequest(FundResolution resolution, string symbol)
{
Date = DateOnly, // a single trading day
From = DateOnly, // window start (inclusive)
To = DateOnly, // window end (inclusive)
Countback = int // N candles back from `To` (or from today), instead of `From`
}
```
The same date-window rules as [stock candles](https://www.marketdata.app/docs/sdk/csharp/stocks/candles) apply and are validated before any HTTP call.
### FundResolution
Fund NAVs are published daily, so the resolutions are daily and coarser:
```csharp
FundResolution.Daily // also Weekly, Monthly, Yearly
FundResolution.Days(1) // also Weeks(n), Months(n), Years(n)
FundResolution.Of("W") // any raw wire token
```
#### Returns
`FundCandlesResponse` wrapping `IReadOnlyList`:
```csharp
public record FundCandle(
DateTimeOffset? Time, // America/New_York
decimal? Open,
decimal? High,
decimal? Low,
decimal? Close);
```
## Examples
```csharp
using MarketDataApp;
using MarketDataApp.Funds;
using var client = await MarketDataClient.CreateAsync();
// The last 30 daily NAVs.
var candles = await client.Funds.GetCandlesAsync(FundResolution.Daily, "VFINX", countback: 30);
foreach (var bar in candles.Values)
{
Console.WriteLine($"{bar.Time:yyyy-MM-dd} NAV close={bar.Close}");
}
```
For CSV output, call `client.Funds.GetCandlesCsvAsync(...)` and read `.Csv`. See [Settings](https://www.marketdata.app/docs/sdk/csharp/settings#csv-output).
---
# Installation (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/installation/index.md
This guide will help you install the Market Data C#/.NET SDK and configure it for your project.
| | |
|-----------------------|------------------------------------------------------------------------------|
| **Package** | [`MarketDataApp`](https://www.nuget.org/packages/MarketDataApp) on NuGet.org |
| **Target frameworks** | `net8.0`, `net10.0` |
| **License** | MIT |
| **Source** | [MarketDataApp/sdk-csharp](https://github.com/MarketDataApp/sdk-csharp) |
## Prerequisites
- **.NET 8.0 or newer.** The package multi-targets `net8.0` and `net10.0` (both LTS releases), so projects on either runtime get a native build.
- The [.NET SDK](https://dotnet.microsoft.com/download) command line, or an IDE such as Visual Studio, Rider, or VS Code with the C# Dev Kit.
## Basic Installation
Add the SDK as a NuGet package reference. From your project directory:
```bash
dotnet add package MarketDataApp
```
`dotnet add package` resolves the newest stable version for you and writes it into your project file.
If you maintain the reference by hand, take the version number from the package's [NuGet page](https://www.nuget.org/packages/MarketDataApp) or the [releases list](https://github.com/MarketDataApp/sdk-csharp/releases) — versions are derived from git tags, so there is no fixed number to copy from the docs:
```xml
```
> [!NOTE]
> The NuGet **package id**, the assembly, and the root **namespace** are all `MarketDataApp` — you install `MarketDataApp` and then write `using MarketDataApp;`. Endpoint-specific types live under `MarketDataApp.Stocks`, `MarketDataApp.Options`, `MarketDataApp.Funds`, `MarketDataApp.Markets`, and `MarketDataApp.Utilities`.
## Requirements & Dependencies
The SDK stays on the standard Microsoft stack, with one small helper:
- **No third-party HTTP client.** It uses the built-in `System.Net.Http.HttpClient`, and can either create its own or use one your application manages (including clients from `IHttpClientFactory`, via `Microsoft.Extensions.Http`).
- **`System.Text.Json`** for JSON decoding — no Newtonsoft dependency.
- **`Microsoft.Extensions.Configuration`** (with the user-secrets provider), **`Microsoft.Extensions.Logging`**, and **`Microsoft.Extensions.DependencyInjection`** for configuration binding, structured logging, and the `AddMarketDataClient` service-collection extension. Environment variables are read by the SDK's own provider, which copies in only the `MARKETDATA_`-prefixed variables, so the rest of the machine environment never reaches the configuration object.
- **`DotNetEnv`** to load an optional `.env` file from the working directory.
## Updating the SDK
To move to the latest stable version:
```bash
dotnet add package MarketDataApp
```
The SDK follows [semantic versioning](https://semver.org/) from `1.0.0`, so a `1.x` update never contains a breaking change. Review the [release notes](https://github.com/MarketDataApp/sdk-csharp/releases) before moving to a new major version.
## Next Steps
After installation, you'll need to:
1. Set up your [authentication token](https://www.marketdata.app/docs/sdk/csharp/authentication).
2. Learn about the [client](https://www.marketdata.app/docs/sdk/csharp/client) and how to make your first API requests.
3. Configure [Settings](https://www.marketdata.app/docs/sdk/csharp/settings) to customize output format, date format, and other universal parameters.
---
# Markets (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/markets/index.md
The C#/.NET SDK from Market Data provides methods to streamline your use of the Markets endpoints. These methods provide a typed interface over the underlying HTTP requests and responses; every method is asynchronous and accepts an optional `CancellationToken`.
Reach the resource through `client.Markets`. For CSV output, call the paired `*CsvAsync` method of the same name (for example `GetStatusCsvAsync`).
## Markets Endpoints
---
# Market Status (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/markets/status/index.md
Retrieve whether a country's stock market is open or closed on a given day, or over a range of days — the market calendar.
## Making Requests
Use `GetStatusAsync` on the `Markets` resource. With no parameters it returns today's status for the US market.
```csharp
Task GetStatusAsync(
string? country = null,
DateOnly? date = null, DateOnly? from = null, DateOnly? to = null, int? countback = null,
MarketDataRequestOptions? options = null, CancellationToken cancellationToken = default)
Task GetStatusAsync(MarketStatusRequest request, ...)
```
### MarketStatusRequest
```csharp
new MarketStatusRequest
{
Country = string, // ISO 3166 two-letter code; default US
Date = DateOnly, // one day
From = DateOnly, // range start
To = DateOnly, // range end
Countback = int // N most recent days
}
```
#### Returns
`MarketStatusResponse` wrapping `IReadOnlyList` — one row per day:
```csharp
public record MarketStatus(
DateTimeOffset? Date,
string? Status); // "open" or "closed"
```
## Examples
```csharp
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
// Is the US market open today?
var today = (await client.Markets.GetStatusAsync("US")).Values[0];
Console.WriteLine($"US market is {today.Status} on {today.Date:yyyy-MM-dd}");
// The last five trading-calendar days.
var recent = await client.Markets.GetStatusAsync("US", countback: 5);
foreach (var day in recent.Values)
{
Console.WriteLine(day); // 2026-08-14 open
}
```
For CSV output, call `client.Markets.GetStatusCsvAsync(...)` and read `.Csv`. See [Settings](https://www.marketdata.app/docs/sdk/csharp/settings#csv-output).
---
# Options (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/options/index.md
The C#/.NET SDK from Market Data provides methods to streamline your use of the Options endpoints. These methods provide a typed interface over the underlying HTTP requests and responses; every method is asynchronous and accepts an optional `CancellationToken`.
Reach the resource through `client.Options`. For CSV output, call the paired `*CsvAsync` method of the same name (for example `GetChainCsvAsync`).
## Options Endpoints
---
# Option Chain (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/options/chain/index.md
Retrieve a complete or filtered options chain for an underlying symbol. Every contract comes back as a full option quote — price, size, volume, open interest, greeks, and IV.
## Making Requests
Use `GetChainAsync` on the `Options` resource. The scalar overload exposes every filter as a named optional parameter; the request-object overload groups them on `OptionsChainRequest`.
```csharp
Task GetChainAsync(
string symbol,
ExpirationFilter? expiration = null,
bool? weekly = null, bool? monthly = null, bool? quarterly = null,
bool? am = null, bool? pm = null, bool? nonStandard = null,
StrikeFilter? strike = null, double? delta = null, int? strikeLimit = null,
StrikeRange? strikeRangeFilter = null,
decimal? minBid = null, decimal? maxBid = null, decimal? minAsk = null, decimal? maxAsk = null,
decimal? maxBidAskSpread = null, double? maxBidAskSpreadPct = null,
long? minOpenInterest = null, long? minVolume = null,
OptionSide? side = null, DateOnly? date = null,
MarketDataRequestOptions? options = null, CancellationToken cancellationToken = default)
Task GetChainAsync(OptionsChainRequest request, ...)
```
> [!CAUTION]
> **[Quota]**
>
> A chain response bills roughly **one credit per contract returned**. An unfiltered chain for a liquid underlying can run to thousands of contracts. Always narrow with an expiration filter, `StrikeLimit`, or `Side` unless you really need the whole chain.
### OptionsChainRequest
```csharp
new OptionsChainRequest(string symbol)
{
// Expiration selection (pick one)
Expiration = ExpirationFilter, // see below
Weekly = bool, Monthly = bool, Quarterly = bool, // expiration cycles
Am = bool, Pm = bool, // settlement time
NonStandard = bool, // include non-standard contracts
// Strike selection
Strike = StrikeFilter, // see below
StrikeLimit = int, // N strikes closest to the money
StrikeRangeFilter = StrikeRange, // StrikeRange.Itm / .Otm / .All
Delta = double, // strikes nearest this delta
// Liquidity filters
MinBid = decimal, MaxBid = decimal, MinAsk = decimal, MaxAsk = decimal,
MaxBidAskSpread = decimal, MaxBidAskSpreadPct = double,
MinOpenInterest = long, MinVolume = long,
Side = OptionSide.Call | OptionSide.Put,
Date = DateOnly // historical chain as of this date
}
```
### ExpirationFilter
A typed value for the `expiration` parameter:
```csharp
ExpirationFilter.ForDate(DateOnly date) // one exact expiration
ExpirationFilter.ForDte(int days) // the expiration closest to N days out
ExpirationFilter.ForRange(DateOnly from, DateOnly to) // every expiration in a window
ExpirationFilter.ForMonthYear(int year, int month) // all expirations in a month
```
### StrikeFilter
```csharp
StrikeFilter.ForExact(decimal price) // one strike
StrikeFilter.ForRange(decimal min, decimal max) // strikes in a band
StrikeFilter.ForComparison(StrikeFilter.ComparisonOperator.Gte, decimal price) // Gt / Gte / Lt / Lte
```
#### Returns
`OptionsChainResponse` wrapping `IReadOnlyList` — the same record the [quotes](https://www.marketdata.app/docs/sdk/csharp/options/quotes) endpoint returns, one per contract:
```csharp
public record OptionQuote(
string? OptionSymbol, string? Underlying,
DateTimeOffset? Expiration, string? Side, decimal? Strike,
DateTimeOffset? FirstTraded, int? Dte, DateTimeOffset? Updated,
decimal? Bid, long? BidSize, decimal? Mid, decimal? Ask, long? AskSize, decimal? Last,
long? OpenInterest, long? Volume,
bool? InTheMoney, decimal? IntrinsicValue, decimal? ExtrinsicValue, decimal? UnderlyingPrice,
double? Iv, double? Delta, double? Gamma, double? Theta, double? Vega, double? Rho);
```
`OptionQuote.PresentGreeks` returns the set of greeks that are non-null on a given row (for example, `Rho` can be null).
## Examples
```csharp
using MarketDataApp;
using MarketDataApp.Options;
using var client = await MarketDataClient.CreateAsync();
// Calls expiring closest to 45 days out, six strikes around the money.
var chain = await client.Options.GetChainAsync(
"AAPL",
expiration: ExpirationFilter.ForDte(45),
strikeLimit: 6,
side: OptionSide.Call);
foreach (var contract in chain.Values)
{
Console.WriteLine(
$"{contract.OptionSymbol} strike={contract.Strike} bid/ask={contract.Bid}/{contract.Ask} " +
$"OI={contract.OpenInterest} IV={contract.Iv:P1} delta={contract.Delta:F2}" +
(contract.InTheMoney == true ? " ITM" : ""));
}
// A liquidity-filtered request object: monthly puts, in the money, tight spreads only.
var puts = await client.Options.GetChainAsync(new OptionsChainRequest("SPY")
{
Monthly = true,
Side = OptionSide.Put,
StrikeRangeFilter = StrikeRange.Itm,
MaxBidAskSpreadPct = 0.05,
MinOpenInterest = 100
});
```
For CSV output, call `client.Options.GetChainCsvAsync(...)` and read `.Csv`. See [Settings](https://www.marketdata.app/docs/sdk/csharp/settings#csv-output).
---
# Expirations (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/options/expirations/index.md
Retrieve the list of expiration dates that have listed options for an underlying symbol.
## Making Requests
Use `GetExpirationsAsync` on the `Options` resource.
```csharp
Task GetExpirationsAsync(
string symbol, decimal? strike = null, DateOnly? date = null,
MarketDataRequestOptions? options = null, CancellationToken cancellationToken = default)
Task GetExpirationsAsync(OptionsExpirationsRequest request, ...)
```
### OptionsExpirationsRequest
```csharp
new OptionsExpirationsRequest(string symbol)
{
Strike = decimal, // only expirations that list this strike
Date = DateOnly // expirations available as of this historical date
}
```
#### Returns
`OptionsExpirationsResponse` wrapping `IReadOnlyList` — one entry per expiration date, in ascending order.
## Examples
```csharp
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
var expirations = await client.Options.GetExpirationsAsync("AAPL");
Console.WriteLine($"{expirations.Values.Count} expirations; next: {expirations.Values[0]:yyyy-MM-dd}");
// Only expirations that list the 200 strike.
var atStrike = await client.Options.GetExpirationsAsync("AAPL", strike: 200m);
```
For CSV output, call `client.Options.GetExpirationsCsvAsync(...)` and read `.Csv`. See [Settings](https://www.marketdata.app/docs/sdk/csharp/settings#csv-output).
---
# Lookup (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/options/lookup/index.md
Turn a human-readable option description into a well-formed OCC option symbol.
## Making Requests
Use `GetLookupAsync` on the `Options` resource.
```csharp
Task GetLookupAsync(
string userInput,
MarketDataRequestOptions? options = null, CancellationToken cancellationToken = default)
Task GetLookupAsync(OptionsLookupRequest request, ...)
```
### OptionsLookupRequest
```csharp
new OptionsLookupRequest(string userInput)
```
#### Returns
`OptionsLookupResponse` wrapping a `string` — the OCC option symbol. Read it with `.Values`.
## Examples
```csharp
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
var lookup = await client.Options.GetLookupAsync("AAPL 12/17/2027 250 Call");
Console.WriteLine(lookup.Values); // AAPL271217C00250000
```
For CSV output, call `client.Options.GetLookupCsvAsync(...)` and read `.Csv`. See [Settings](https://www.marketdata.app/docs/sdk/csharp/settings#csv-output).
---
# Option Quotes (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/options/quotes/index.md
Retrieve a real-time or historical quote for a specific option contract, identified by its OCC option symbol.
## Making Requests
The `Options` resource offers `GetQuoteAsync` for one contract and `GetQuotesAsync` for several. Unlike stock quotes, the options backend does not batch symbols: `GetQuotesAsync` fans out one request per contract (bounded by the client's `MaxConcurrentRequests`) and returns a dictionary keyed by option symbol, so each contract keeps its own response metadata.
```csharp
// One contract
Task GetQuoteAsync(
string optionSymbol, DateOnly? date = null, DateOnly? from = null, DateOnly? to = null,
MarketDataRequestOptions? options = null, CancellationToken cancellationToken = default)
Task GetQuoteAsync(OptionsQuoteRequest request, ...)
// Several contracts, one request each
Task> GetQuotesAsync(
string[] optionSymbols, DateOnly? date = null, DateOnly? from = null, DateOnly? to = null,
MarketDataRequestOptions? options = null, CancellationToken cancellationToken = default)
Task> GetQuotesAsync(OptionsQuotesRequest request, ...)
```
### Request types
```csharp
new OptionsQuoteRequest(string optionSymbol)
{
Date = DateOnly, // a single historical date
From = DateOnly, // historical window start
To = DateOnly // historical window end
}
new OptionsQuotesRequest(params string[] optionSymbols) // or any IEnumerable
{
Date = DateOnly, From = DateOnly, To = DateOnly
}
```
#### Returns
`OptionsQuotesResponse` wrapping `IReadOnlyList` — a single row for a live quote, or one row per trading day for a historical window. See [chain](https://www.marketdata.app/docs/sdk/csharp/options/chain#returns) for the full `OptionQuote` record.
## Examples
```csharp
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
// Live quote for one contract.
var quote = (await client.Options.GetQuoteAsync("AAPL271217C00250000")).Values[0];
Console.WriteLine($"{quote.OptionSymbol}: bid/ask={quote.Bid}/{quote.Ask} IV={quote.Iv:P1} delta={quote.Delta:F2}");
// Several contracts — one response per symbol.
var quotes = await client.Options.GetQuotesAsync(["AAPL271217C00250000", "AAPL271217P00250000"]);
foreach (var (symbol, response) in quotes)
{
Console.WriteLine($"{symbol}: mid={response.Values[0].Mid} (request {response.RequestId})");
}
```
For CSV output, call `client.Options.GetQuoteCsvAsync(...)` and read `.Csv`. See [Settings](https://www.marketdata.app/docs/sdk/csharp/settings#csv-output).
---
# Settings (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/settings/index.md
The SDK is configured at two levels: **client options** (`MarketDataClientOptions`, applied once when the client is created) and **per-request options** (`MarketDataRequestOptions`, passed to any endpoint call). Client options can be set programmatically or bound from configuration; per-request options override the client-level defaults field by field.
## Configuration Cascade
When you create a client without explicit options, `MarketDataClientOptions.FromEnvironment()` loads every `MARKETDATA_*` key from these sources, in **increasing precedence order**:
1. .NET user secrets (lowest priority)
2. An optional `.env` file in the current working directory
3. `MARKETDATA_*` environment variables (highest priority)
So a `MARKETDATA_*` environment variable overrides the same key in `.env`, which overrides user secrets. Only the `MARKETDATA_`-prefixed variables are read; every other variable in the environment is ignored. Explicit options — `new MarketDataClientOptions { ... }` — bypass the cascade entirely.
In ASP.NET Core and generic-host apps, `MarketDataClientOptions.FromConfiguration(IConfiguration)` reads the same keys from your app's `IConfiguration` (appsettings, user secrets, environment variables — whatever the host has wired up). `AddMarketDataClient(builder.Configuration)` does this for you.
The **per-request** layer sits on top: any `MarketDataRequestOptions` field you set on an endpoint call wins over the matching client-level default; fields you leave `null` fall back to the client default.
```csharp
// MARKETDATA_DATE_FORMAT=timestamp and MARKETDATA_MODE=cached configured in the environment.
using var client = await MarketDataClient.CreateAsync();
// dateformat=timestamp and mode=cached are applied from the client defaults.
await client.Stocks.GetPricesAsync(["AAPL"]);
// dateformat=unix wins from the per-request options; mode=cached still fills from the default.
await client.Stocks.GetPricesAsync(
["AAPL"],
new MarketDataRequestOptions { DateFormat = DateFormat.Unix });
```
## Universal Parameters
`MarketDataRequestOptions` carries the parameters every data endpoint understands. Pass it as the `options` argument of any endpoint method:
```csharp
var response = await client.Stocks.GetQuotesAsync(
["AAPL", "MSFT"],
options: new MarketDataRequestOptions
{
DateFormat = DateFormat.Timestamp,
Mode = Mode.Delayed,
Limit = 50,
Columns = ["symbol", "last"]
});
```
### Date Format
`DateFormat` controls how the API renders date/time fields in the raw response. Client-level default: `DefaultDateFormat` (`MARKETDATA_DATE_FORMAT`).
| Value | Wire value | Notes |
|--------------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------|
| `DateFormat.Unix` | `unix` | Unix epoch seconds. |
| `DateFormat.Timestamp` | `timestamp` | ISO-8601 timestamp strings. |
| `DateFormat.Spreadsheet` | `spreadsheet` | Excel/Sheets serial dates. **CSV-only**: typed methods reject it with `ArgumentException`, because the typed decoders cannot parse serial dates. |
Regardless of the wire format, typed responses always expose dates as `DateTimeOffset` values normalized to **America/New_York**.
### Data Mode
`Mode` selects the data freshness. Client-level default: `DefaultMode` (`MARKETDATA_MODE`).
| Value | Wire value |
|----------------|------------|
| `Mode.Live` | `live` |
| `Mode.Delayed` | `delayed` |
| `Mode.Cached` | `cached` |
### Columns
`Columns` restricts the response to the named fields, which reduces both payload size and, on some endpoints, the credits consumed. It applies to both typed and CSV requests. Fields projected away come back as `null` on the typed models — which is why every field on the response records is nullable. Client-level default: `DefaultColumns` (`MARKETDATA_COLUMNS`, comma-separated).
### Limit and Offset
`Limit` caps the number of rows and `Offset` skips rows, for endpoints that support paging. They have no client-level default and are taken only from the per-request options.
### Headers and Human
`Headers` (include a header row) and `Human` (human-readable field names and values) apply to **CSV output only**. Typed JSON responses are always decoded into typed models keyed by property name, so neither flag is sent on the typed request path. Client-level defaults: `DefaultAddHeaders` (`MARKETDATA_ADD_HEADERS`) and `DefaultHuman` (`MARKETDATA_USE_HUMAN_READABLE`).
## CSV Output
Every endpoint has a paired `*CsvAsync` method that returns the API's CSV output as text instead of typed models. The output format is chosen by **which method you call**, not by a setting: `GetQuotesAsync` returns typed models, `GetQuotesCsvAsync` returns CSV.
```csharp
var csv = await client.Stocks.GetPricesCsvAsync(
["AAPL", "MSFT"],
new MarketDataRequestOptions { Headers = true, Human = true });
Console.WriteLine(csv.Csv); // the CSV text (also available as Values / RawBody)
await csv.SaveToFileAsync("prices.csv"); // write it to disk
```
`CsvResponse` carries the same metadata as every other response (`StatusCode`, `RequestId`, `RateLimit`, `IsNoData`, ...), with `IsCsv == true`.
> [!NOTE]
> **[`MARKETDATA_OUTPUT_FORMAT`]**
>
> This key exists for parity with the other Market Data SDKs and is **advisory only** in C#: it stores a hint on `OutputFormat` but never reroutes a typed method to CSV or vice versa. Call the `*CsvAsync` method when you want CSV.
## Logging
The SDK emits structured `Microsoft.Extensions.Logging` events for its lifecycle, requests, responses, retries, and errors — tokens are always redacted. Attach any `ILogger` through `Logger` (or `options.WithLogger(logger)`); in ASP.NET Core, `AddMarketDataClient` auto-wires the container's `ILogger` when logging is configured.
`MinimumLogLevel` (`MARKETDATA_LOGGING_LEVEL`) controls the SDK's own verbosity. The default `Information` suppresses the per-request Debug logs unless you set `MARKETDATA_LOGGING_LEVEL=DEBUG`. Accepted values are `DEBUG` / `INFO` / `WARNING` / `ERROR` or any .NET `LogLevel` name.
For the canonical `{timestamp} - {logger_name} - {level} - {message}` console line shared by the Market Data SDKs, add the opt-in formatter to your logging builder:
```csharp
builder.Logging.AddMarketDataCanonicalConsole();
// Output: 2025-02-21 12:00:00 - marketdata.client - INFO - Making request...
```
## Programmatic Options
Everything the cascade can bind is also settable in code, plus a few knobs that are code-only:
```csharp
var options = new MarketDataClientOptions
{
ApiToken = token,
MaxRetries = 2, // the only retry knob (default 3)
MaxConcurrentRequests = 10, // 1–50, default 50
ValidateTokenOnStartup = false, // default true
TimeProvider = TimeProvider.System, // inject a fake clock in tests
UserAgent = "my-app/1.0",
Logger = logger,
DefaultDateFormat = DateFormat.Timestamp,
DefaultMode = Mode.Cached
};
```
Retry timing (the 1-second base delay, exponential growth, 30-second cap, and the `Retry-After` ceiling) is fixed by the SDK requirements and is not configurable.
## Environment Variables Reference
| Key | Property | Default | Notes |
|--------------------------------------|-------------------------|-----------------------------------|-----------------------------------------|
| `MARKETDATA_TOKEN` | `ApiToken` | `null` | Bearer token; `null` means demo mode. |
| `MARKETDATA_BASE_URL` | `BaseAddress` | `https://api.marketdata.app/` | Absolute HTTP(S) URI. |
| `MARKETDATA_API_VERSION` | `ApiVersion` | `v1` | Version path segment. |
| `MARKETDATA_MAX_RETRIES` | `MaxRetries` | `3` | Retries after a transient failure. |
| `MARKETDATA_MAX_CONCURRENT_REQUESTS` | `MaxConcurrentRequests` | `50` | In-flight request cap, 1–50. |
| `MARKETDATA_USER_AGENT` | `UserAgent` | `marketdata-sdk-csharp/{version}` | Sent on every request. |
| `MARKETDATA_DATE_FORMAT` | `DefaultDateFormat` | `null` | `unix` / `timestamp` / `spreadsheet`. |
| `MARKETDATA_MODE` | `DefaultMode` | `null` | `live` / `delayed` / `cached`. |
| `MARKETDATA_COLUMNS` | `DefaultColumns` | `null` | Comma-separated column list. |
| `MARKETDATA_ADD_HEADERS` | `DefaultAddHeaders` | `null` | `true` / `false`; CSV only. |
| `MARKETDATA_USE_HUMAN_READABLE` | `DefaultHuman` | `null` | `true` / `false`; CSV only. |
| `MARKETDATA_LOGGING_LEVEL` | `MinimumLogLevel` | `Information` | `DEBUG` / `INFO` / `WARNING` / `ERROR`. |
| `MARKETDATA_OUTPUT_FORMAT` | `OutputFormat` | `null` | `json` / `csv`; advisory only. |
Invalid values for any key throw a `FormatException` naming the offending key.
---
# Stocks (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/stocks/index.md
The C#/.NET SDK from Market Data provides methods to streamline your use of the Stocks endpoints. These methods provide a typed interface over the underlying HTTP requests and responses; every method is asynchronous and accepts an optional `CancellationToken`.
Reach the resource through `client.Stocks`. For CSV output, call the paired `*CsvAsync` method of the same name (for example `GetQuotesCsvAsync`).
## Stocks Endpoints
---
# Stock Candles (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/stocks/candles/index.md
Retrieve historical OHLCV (open/high/low/close/volume) candles for a stock symbol.
## Making Requests
Use `GetCandlesAsync` on the `Stocks` resource, passing a `StockResolution` and a symbol. For large intraday ranges the SDK automatically splits the request into year-sized chunks, fetches them concurrently, and merges the results into a single response (`IsComposite == true`, one entry in `Parts` per HTTP request).
```csharp
// Scalar overload — the common parameters directly:
Task GetCandlesAsync(
StockResolution resolution, string symbol,
DateOnly? date = null, DateOnly? from = null, DateOnly? to = null, int? countback = null,
string? exchange = null, bool? extended = null, string? country = null,
bool? adjustSplits = null, bool? adjustDividends = null,
MarketDataRequestOptions? options = null, CancellationToken cancellationToken = default)
// Request-object overload:
Task GetCandlesAsync(
StockCandlesRequest request,
MarketDataRequestOptions? options = null, CancellationToken cancellationToken = default)
```
### StockCandlesRequest
```csharp
new StockCandlesRequest(StockResolution resolution, string symbol)
{
Date = DateOnly, // a single trading day
From = DateOnly, // window start (inclusive)
To = DateOnly, // window end (inclusive)
Countback = int, // N candles back from `To` (or from today), instead of `From`
Exchange = string, // disambiguate exchange
Extended = bool, // include extended-hours bars (intraday)
Country = string, // exchange country (ISO 3166, 2-letter)
AdjustSplits = bool, // default: true for daily
AdjustDividends = bool // default: true for daily
}
```
The date window is validated before any HTTP call: `Date` is exclusive with `From`/`To`/`Countback`, and `Countback` cannot be combined with `From`. An invalid combination throws `ArgumentException`.
### StockResolution
A value type for the candle interval:
```csharp
StockResolution.Daily // also Weekly, Monthly, Yearly
StockResolution.Minutes(5) // 5-minute bars
StockResolution.Hours(1) // hourly bars
StockResolution.Days(1) // also Weeks(n), Months(n), Years(n)
StockResolution.Of("1H") // any raw wire token
```
#### Returns
`StockCandlesResponse` wrapping `IReadOnlyList`:
```csharp
public record StockCandle(
DateTimeOffset? Time, // bar opening moment (America/New_York)
decimal? Open,
decimal? High,
decimal? Low,
decimal? Close,
long? Volume);
```
## Examples
```csharp
using MarketDataApp;
using MarketDataApp.Stocks;
using var client = await MarketDataClient.CreateAsync();
// Daily candles over a date range.
var today = DateOnly.FromDateTime(DateTime.Today);
var candles = await client.Stocks.GetCandlesAsync(
StockResolution.Daily, "AAPL",
from: today.AddDays(-7),
to: today);
foreach (var bar in candles.Values)
{
Console.WriteLine(bar); // 2026-08-14 O=228.10 H=230.44 L=227.55 C=229.88 V=41203118
}
// The last 10 sessions, using countback instead of a left edge.
var lastTen = await client.Stocks.GetCandlesAsync(StockResolution.Daily, "AAPL", countback: 10);
// A long intraday range: the SDK chunks it by year and merges the parts.
var intraday = await client.Stocks.GetCandlesAsync(
new StockCandlesRequest(StockResolution.Minutes(30), "AAPL")
{
From = today.AddYears(-2),
To = today
});
Console.WriteLine($"{intraday.Values.Count} bars from {intraday.Parts.Count} requests");
```
For CSV output, call `client.Stocks.GetCandlesCsvAsync(...)` with the same parameters and read `.Csv`. See [Settings](https://www.marketdata.app/docs/sdk/csharp/settings#csv-output).
---
# Earnings (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/stocks/earnings/index.md
Retrieve historical and upcoming earnings reports for a stock symbol: fiscal period, report date and time, and reported vs. estimated EPS.
## Making Requests
Use `GetEarningsAsync` on the `Stocks` resource.
```csharp
Task GetEarningsAsync(
string symbol,
DateOnly? date = null, DateOnly? from = null, DateOnly? to = null, int? countback = null,
string? report = null,
MarketDataRequestOptions? options = null, CancellationToken cancellationToken = default)
Task GetEarningsAsync(StockEarningsRequest request, ...)
```
### StockEarningsRequest
```csharp
new StockEarningsRequest(string symbol)
{
Date = DateOnly, // a single date
From = DateOnly, // window start
To = DateOnly, // window end
Countback = int, // N most recent reports
Report = string // a specific report, e.g. "2024-Q1"
}
```
The same date-window rules as [candles](https://www.marketdata.app/docs/sdk/csharp/stocks/candles) apply and are validated before any HTTP call.
#### Returns
`StockEarningsResponse` wrapping `IReadOnlyList`:
```csharp
public record StockEarning(
string? Symbol,
int? FiscalYear,
int? FiscalQuarter,
DateTimeOffset? Date,
DateTimeOffset? ReportDate,
string? ReportTime, // e.g. "before market open" / "after market close"
string? Currency,
decimal? ReportedEps,
decimal? EstimatedEps,
decimal? SurpriseEps,
double? SurpriseEpsPct,
DateTimeOffset? Updated);
```
## Examples
```csharp
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
// The four most recent reports.
var earnings = await client.Stocks.GetEarningsAsync("AAPL", countback: 4);
foreach (var report in earnings.Values)
{
Console.WriteLine(
$"FY{report.FiscalYear} Q{report.FiscalQuarter}: reported {report.ReportedEps} vs est. {report.EstimatedEps} " +
$"({report.SurpriseEpsPct:P1} surprise) on {report.ReportDate:yyyy-MM-dd}");
}
```
For CSV output, call `client.Stocks.GetEarningsCsvAsync(...)` and read `.Csv`. See [Settings](https://www.marketdata.app/docs/sdk/csharp/settings#csv-output).
---
# News (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/stocks/news/index.md
Retrieve news articles for a stock symbol.
## Making Requests
Use `GetNewsAsync` on the `Stocks` resource.
```csharp
Task GetNewsAsync(
string symbol,
DateOnly? date = null, DateOnly? from = null, DateOnly? to = null, int? countback = null,
MarketDataRequestOptions? options = null, CancellationToken cancellationToken = default)
Task GetNewsAsync(StockNewsRequest request, ...)
```
### StockNewsRequest
```csharp
new StockNewsRequest(string symbol)
{
Date = DateOnly, // a single date
From = DateOnly, // window start
To = DateOnly, // window end
Countback = int // N most recent articles
}
```
The same date-window rules as [candles](https://www.marketdata.app/docs/sdk/csharp/stocks/candles) apply and are validated before any HTTP call.
#### Returns
`StockNewsResponse` wrapping `IReadOnlyList`:
```csharp
public record StockNewsArticle(
string Symbol,
string Headline,
string Content,
string Source,
DateTimeOffset PublicationDate); // America/New_York
```
## Examples
```csharp
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
var news = await client.Stocks.GetNewsAsync("AAPL", countback: 5);
foreach (var article in news.Values)
{
Console.WriteLine($"[{article.PublicationDate:yyyy-MM-dd}] {article.Source}: {article.Headline}");
}
```
For CSV output, call `client.Stocks.GetNewsCsvAsync(...)` and read `.Csv`. See [Settings](https://www.marketdata.app/docs/sdk/csharp/settings#csv-output).
---
# Prices (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/stocks/prices/index.md
Retrieve the latest price (mid, change) for one or more stock symbols — a lighter payload than a full quote.
## Making Requests
The `Stocks` resource offers `GetPriceAsync` for a single symbol and `GetPricesAsync` for several symbols in one request.
```csharp
Task GetPriceAsync(
string symbol,
MarketDataRequestOptions? options = null, CancellationToken cancellationToken = default)
Task GetPriceAsync(StockPriceRequest request, ...)
Task GetPricesAsync(
string[] symbols,
MarketDataRequestOptions? options = null, CancellationToken cancellationToken = default)
Task GetPricesAsync(StockPricesRequest request, ...)
```
### Request types
```csharp
new StockPriceRequest(string symbol)
new StockPricesRequest(params string[] symbols) // or any IEnumerable
```
#### Returns
`StockPricesResponse` wrapping `IReadOnlyList`:
```csharp
public record StockPrice(
string? Symbol,
decimal? Mid,
decimal? Change,
double? ChangePct, // a fraction: 0.0123 == +1.23%
DateTimeOffset? Updated); // America/New_York
```
## Examples
```csharp
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
var prices = await client.Stocks.GetPricesAsync(["AAPL", "MSFT", "GOOG"]);
foreach (var price in prices.Values)
{
Console.WriteLine($"{price.Symbol}: {price.Mid} ({price.ChangePct:P2})");
}
```
For CSV output, call `client.Stocks.GetPriceCsvAsync(...)` or `GetPricesCsvAsync(...)` and read `.Csv`. See [Settings](https://www.marketdata.app/docs/sdk/csharp/settings#csv-output).
---
# Stock Quotes (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/stocks/quotes/index.md
Retrieve real-time quotes (bid, ask, mid, last, volume, etc.) for one or more stock symbols.
## Making Requests
The `Stocks` resource offers two quote methods:
- `GetQuoteAsync(...)` — a single symbol.
- `GetQuotesAsync(...)` — several symbols in **one** request (the stocks backend batches a comma list). The result is a single response with one row per symbol.
```csharp
// Single symbol
Task GetQuoteAsync(
string symbol, bool? extended = null, bool? candle = null, bool? week52 = null,
MarketDataRequestOptions? options = null, CancellationToken cancellationToken = default)
Task GetQuoteAsync(StockQuoteRequest request, ...)
// Multiple symbols, one request
Task GetQuotesAsync(
string[] symbols, bool? extended = null, bool? candle = null, bool? week52 = null,
MarketDataRequestOptions? options = null, CancellationToken cancellationToken = default)
Task GetQuotesAsync(StockQuotesRequest request, ...)
```
### Request types
```csharp
// Single symbol
new StockQuoteRequest(string symbol)
{
Extended = bool, // include extended-session prices
Candle = bool, // add OHLC columns
Week52 = bool // add 52-week high/low
}
// Multiple symbols, one request
new StockQuotesRequest(params string[] symbols) // or any IEnumerable
{
Extended = bool,
Candle = bool,
Week52 = bool
}
```
#### Returns
`StockQuotesResponse` wrapping `IReadOnlyList` (one element for `GetQuoteAsync`, one per symbol for `GetQuotesAsync`). All fields are nullable: `Columns` can project any field away, and the backend maps NaN to `null` for closed or illiquid markets.
```csharp
public record StockQuote(
string? Symbol,
decimal? Ask, long? AskSize,
decimal? Bid, long? BidSize,
decimal? Mid, decimal? Last,
decimal? Change, double? ChangePct, // ChangePct is a fraction: 0.0123 == +1.23%
long? Volume, DateTimeOffset? Updated, // Updated is America/New_York
// opt-in via Candle = true:
decimal? Open, decimal? High, decimal? Low, decimal? Close,
// opt-in via Week52 = true:
decimal? Week52High, decimal? Week52Low);
```
## Examples
```csharp
using MarketDataApp;
using MarketDataApp.Stocks;
using var client = await MarketDataClient.CreateAsync();
// A single quote — the response is a list; a single symbol is row 0.
var quote = (await client.Stocks.GetQuoteAsync("AAPL")).Values[0];
Console.WriteLine($"{quote.Symbol}: bid={quote.Bid} ask={quote.Ask} last={quote.Last}");
// Several symbols in one request, with the 52-week range opted in.
var quotes = await client.Stocks.GetQuotesAsync(["AAPL", "MSFT", "GOOG"], week52: true);
foreach (var row in quotes.Values)
{
Console.WriteLine($"{row.Symbol}: last={row.Last} 52w {row.Week52Low}–{row.Week52High}");
}
// The same call with a request object, grouping the optional flags.
var detailed = await client.Stocks.GetQuotesAsync(
new StockQuotesRequest("AAPL", "MSFT") { Candle = true, Extended = true });
```
For CSV output, call `client.Stocks.GetQuoteCsvAsync(...)` or `GetQuotesCsvAsync(...)` and read `.Csv`. See [Settings](https://www.marketdata.app/docs/sdk/csharp/settings#csv-output).
---
# Utilities (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/utilities/index.md
The C#/.NET SDK from Market Data provides methods for the Utilities endpoints: API service status, request-header inspection, and authenticated-user information. These are diagnostic endpoints, so they take no request parameters beyond a `CancellationToken` and have no CSV variants.
Reach the resource through `client.Utilities`.
## Utilities Endpoints
---
# Headers (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/utilities/headers/index.md
Echo back the HTTP request headers the API received. Useful for debugging what your application (or a proxy in between) is actually sending — user agent, authorization scheme, content negotiation.
## Making Requests
Use `GetHeadersAsync` on the `Utilities` resource.
```csharp
Task GetHeadersAsync(CancellationToken cancellationToken = default)
```
### Returns
`UtilitiesHeadersResponse` wrapping `IReadOnlyDictionary` — header name to value, as received by the API. The `Authorization` value is returned redacted by the API.
## Examples
```csharp
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
var headers = await client.Utilities.GetHeadersAsync();
foreach (var (name, value) in headers.Values)
{
Console.WriteLine($"{name}: {value}");
}
```
---
# API Status (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/utilities/status/index.md
Retrieve the current operational status of the Market Data API services, with 30- and 90-day uptime. This is a public endpoint that works in demo mode.
## Making Requests
Use `GetStatusAsync` on the `Utilities` resource.
```csharp
Task GetStatusAsync(CancellationToken cancellationToken = default)
```
### Returns
`UtilitiesStatusResponse` wrapping `IReadOnlyList` — one row per service:
```csharp
public record ServiceStatus(
string Service,
string Status,
bool Online,
double UptimePct30d,
double UptimePct90d,
DateTimeOffset Updated);
```
The SDK also uses this endpoint internally: before retrying a server error it consults a cached `/status/` reading and skips the retry when the API reports itself offline. Calling `GetStatusAsync` yourself refreshes that same cache.
## Examples
```csharp
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
var status = await client.Utilities.GetStatusAsync();
foreach (var service in status.Values)
{
Console.WriteLine($"{service.Service}: {service.Status} ({service.UptimePct30d:F2}% over 30d)");
}
Console.WriteLine($"{status.Values.Count(s => s.Online)}/{status.Values.Count} services online");
```
---
# User (C# SDK)
Source: https://www.marketdata.app/docs/sdk/csharp/utilities/user/index.md
Retrieve the authenticated account's details: request quota and options-data entitlement. Requires a token.
## Making Requests
Use `GetUserAsync` on the `Utilities` resource.
```csharp
Task GetUserAsync(CancellationToken cancellationToken = default)
```
### Returns
`UtilitiesUserResponse` wrapping a single `User` record:
```csharp
public record User(
int RequestsRemaining, // requests left in the current billing period
int RequestsLimit, // total quota for the billing period
string OptionsDataPermissions); // "" == real-time; otherwise describes the delay
```
> [!NOTE]
> **[Rate-limit state]**
>
> This endpoint is also what the client calls at startup to validate your token. The rate-limit numbers the SDK tracks — including the reset time and consumed count — come from the `x-api-ratelimit-*` response headers, not from this body, and surface through [`client.LatestRateLimit`](https://www.marketdata.app/docs/sdk/csharp/client#rate-limits) and `response.RateLimit`. `User` is the typed view of the account body only.
## Examples
```csharp
using MarketDataApp;
using var client = await MarketDataClient.CreateAsync();
var user = (await client.Utilities.GetUserAsync()).Values;
Console.WriteLine($"Quota: {user.RequestsRemaining}/{user.RequestsLimit} remaining");
Console.WriteLine(string.IsNullOrEmpty(user.OptionsDataPermissions)
? "Options data: real-time"
: $"Options data: {user.OptionsDataPermissions}");
```
---
# Go SDK
Source: https://www.marketdata.app/docs/sdk/go/index.md
Welcome to the Market Data **Go SDK v2**. This is a complete, idiomatic rewrite of the SDK that gives you type-safe access to real-time and historical stock, option, fund, and market data with nothing but the Go standard library at runtime.
## Quick Start
Create a client, make a call, and you are done. The client reads your token from the `MARKETDATA_TOKEN` environment variable (or a `.env` file) automatically, so no token needs to appear in your code:
```go
package main
import (
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
// Reads MARKETDATA_TOKEN from the environment or a .env file.
client, err := marketdata.NewClient()
if err != nil {
log.Fatal(err)
}
defer client.Close()
quote, err := client.Stocks.GetQuote("AAPL")
if err != nil {
log.Fatal(err)
}
fmt.Printf("AAPL last: $%.2f bid: $%.2f ask: $%.2f\n",
quote.Last, quote.Bid, quote.Ask)
}
```
```
AAPL last: $234.07 bid: $234.05 ask: $234.09
```
Install it with `go get` (the import path includes the `/v2` suffix):
```bash
go get github.com/MarketDataApp/sdk-go/v2
```
The SDK requires **Go 1.22 or later** and has **zero third-party runtime dependencies**.
## Open Source
The SDK is open source under the MIT license and is available on GitHub. Feel free to contribute to the project, report bugs, or request new features.
- [Go SDK GitHub](https://github.com/MarketDataApp/sdk-go/)
- [Go SDK on pkg.go.dev](https://pkg.go.dev/github.com/MarketDataApp/sdk-go/v2)
## Documentation
The best source for documentation on the SDK is right here. This documentation is the most up-to-date and accurate source of information on the SDK.
## Using the SDK
This SDK is designed to help you get up and running with Market Data's APIs as quickly as possible, providing you with all the tools you need to access real-time stock and options prices, historical data, and much more.
### Getting Started
1. [Install the SDK](https://www.marketdata.app/docs/sdk/go/installation) into your Go module.
2. Set up your [authentication token](https://www.marketdata.app/docs/sdk/go/authentication) to access the API.
3. Learn about the [client](https://www.marketdata.app/docs/sdk/go/client) and how to make your first API requests.
4. Configure [parameters](https://www.marketdata.app/docs/sdk/go/parameters) to customize mode, columns, pagination, and other universal parameters.
5. Handle failures with the SDK's typed [errors](https://www.marketdata.app/docs/sdk/go/errors), and set up [logging](https://www.marketdata.app/docs/sdk/go/logging) for debugging.
6. Explore the available endpoints for [stocks](https://www.marketdata.app/docs/sdk/go/stocks), [options](https://www.marketdata.app/docs/sdk/go/options), [funds](https://www.marketdata.app/docs/sdk/go/funds), [markets](https://www.marketdata.app/docs/sdk/go/markets), and [utilities](https://www.marketdata.app/docs/sdk/go/utilities).
### Support
- If you have any questions or need further assistance, please don't hesitate to open a ticket at our [helpdesk](https://www.marketdata.app/dashboard/).
- If you find a bug you may also [open an issue in our GitHub repository](https://github.com/MarketDataApp/sdk-go/issues). Please only open issues if you find a bug. Use our helpdesk for general questions or implementation assistance.
---
# Authentication (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/authentication/index.md
The Market Data API authenticates every request with a **Bearer token**. You can obtain a token from the [Market Data Dashboard](https://www.marketdata.app/dashboard/).
The Go SDK resolves your token from several sources so you never have to hard-code it in production. If no token is found, the client starts in **demo mode** with access limited to unauthenticated endpoints and free sample symbols (such as `AAPL`).
## Token Priority
The SDK resolves the token in the following order, highest priority first:
1. **`WithToken(...)`** — an explicit token passed to `NewClient`
2. **`MARKETDATA_TOKEN`** — the process environment variable
3. **`.env` file** — a `MARKETDATA_TOKEN` entry loaded from a `.env` file in the working directory
An explicit `WithToken` always wins over the environment, and a real environment variable always wins over a value loaded from `.env` (loading a `.env` file never overwrites variables already set in the process).
> [!TIP]
> In production, prefer the environment variable so your token is never committed with your source code. Reserve `WithToken` for local experiments and tests.
## Setting the Environment Variable (recommended)
### Mac / Linux
Set the variable for the current shell session:
```bash
export MARKETDATA_TOKEN="your_api_token"
```
To make it persistent, add that line to your shell profile (`~/.bashrc`, `~/.zshrc`, etc.), then reload it:
```bash
source ~/.zshrc
```
Verify it is set:
```bash
echo $MARKETDATA_TOKEN
```
### Windows
Using Command Prompt (persists for future sessions, not the current one):
```batch
setx MARKETDATA_TOKEN "your_api_token"
```
Open a **new** Command Prompt and verify:
```batch
echo %MARKETDATA_TOKEN%
```
With the variable set, create the client with no arguments and it picks up the token automatically:
```go
client, err := marketdata.NewClient()
```
## Using a `.env` File
`NewClient` automatically loads a `.env` file from the current working directory. Create one in your project root:
```ini
MARKETDATA_TOKEN=your_api_token
```
Values in `.env` are only applied when the corresponding variable is **not** already set in the real environment, so an exported `MARKETDATA_TOKEN` takes precedence.
> [!WARNING]
> Add `.env` to your `.gitignore` so your token never lands in version control.
## Passing the Token Explicitly
You can supply the token directly with the `WithToken` option. This overrides both the environment variable and any `.env` value:
```go
package main
import (
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(
marketdata.WithToken("your_api_token"),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
}
```
## Startup Validation
When a token is present, `NewClient` validates it with a single synchronous request to the `/user/` endpoint. If the token is rejected, `NewClient` returns an error that wraps an [`AuthenticationError`](https://www.marketdata.app/docs/sdk/go/errors):
```go
client, err := marketdata.NewClient(marketdata.WithToken("invalid-token"))
if err != nil {
var authErr *marketdata.AuthenticationError
if errors.As(err, &authErr) {
log.Fatalf("invalid API token: %v", authErr)
}
log.Fatal(err)
}
```
If you want to skip this round trip (for example to shave startup latency), pass [`WithoutStartupValidation`](https://www.marketdata.app/docs/sdk/go/client). An invalid token then goes undetected until your first API call fails with an `AuthenticationError`.
## Testing Your Token
Confirm your token works and inspect your current API credit usage with the Utilities service:
```go
package main
import (
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient()
if err != nil {
log.Fatal(err)
}
defer client.Close()
user, err := client.Utilities.GetUser()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Credits remaining: %d / %d\n", user.CreditsRemaining, user.CreditLimit)
fmt.Printf("Window resets at: %s\n", user.ResetAt)
fmt.Printf("Options data: %s\n", user.OptionsDataPermissions)
}
```
> [!TIP]
> When you test authentication, use a symbol that requires it — such as `SPY` — rather than `AAPL`. `AAPL` is a free sample symbol and returns data even when your token is missing or invalid, so it will not reveal an auth problem.
## Security Notes
- The SDK **never logs your full token**. In debug output it is redacted to its last four characters. See [Logging](https://www.marketdata.app/docs/sdk/go/logging).
- The SDK **refuses to transmit your token over an insecure (non-HTTPS) connection** unless the host is a loopback address, guarding against accidental cleartext exposure.
## Next Steps
With authentication configured, read the [Client](https://www.marketdata.app/docs/sdk/go/client) guide to learn how to construct clients and make requests.
---
# Client (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/client/index.md
The `marketdata.Client` is the entry point to the SDK. You create one with `NewClient`, configure it with functional options, and reach every endpoint through its five resource services. A `Client` is safe for concurrent use and caps itself at 50 in-flight requests; additional calls block until a slot frees.
## Creating a Client
```go
func NewClient(opts ...Option) (*Client, error)
```
`NewClient` first loads a `.env` file from the working directory (without overriding real environment variables), resolves your token, and — when a token is present — validates it against the API. Always release the client with `Close` when you are done, typically via `defer`:
```go
package main
import (
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
// Simplest form: reads MARKETDATA_TOKEN from the environment / .env.
client, err := marketdata.NewClient()
if err != nil {
log.Fatal(err)
}
defer client.Close()
}
```
## Functional Options
Pass any number of `Option` values to `NewClient`. Options are applied in order after defaults and environment variables, so a later option overrides an earlier one and any explicit option overrides the environment.
| Option | Description |
|----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`WithToken(token string)`** | Sets the API token. Highest-priority token source; overrides `MARKETDATA_TOKEN` and `.env`. |
| **`WithEnvironment(env Environment)`** | Selects the API environment: `marketdata.Production` (default), `marketdata.Test`, or `marketdata.Development`, each mapped to a base URL. |
| **`WithBaseURL(url string)`** | Points the client at an arbitrary base URL (a proxy or mock server). Must be a valid `http`/`https` URL with a host, or `NewClient` returns a `ValidationError`. |
| **`WithHTTPClient(c *http.Client)`** | Supplies a custom `*http.Client` (custom transport, proxy, instrumentation). The SDK's fixed timeouts still apply. |
| **`WithLogger(l *slog.Logger)`** | Replaces `slog.Default()` as the logger. See [Logging](https://www.marketdata.app/docs/sdk/go/logging). |
| **`WithDebug(enabled bool)`** | Enables verbose request/response debug logging. |
| **`WithoutStartupValidation()`** | Skips the synchronous token-validation request during `NewClient`. |
| **`WithMaxRetries(n int)`** | Sets the maximum retry attempts for failed requests (default 3; `0` disables retries). |
| **`WithMode(m Mode)`** | Sets the universal `mode` parameter (`ModeLive`, `ModeCached`, `ModeDelayed`) on every request. |
| **`WithMaxAge(maxAge string)`** | Sets the universal `maxage` parameter for cached mode (e.g. `"5min"`, `"1h"`). |
| **`WithColumns(cols ...string)`** | Sets the universal `columns` parameter to trim the response. |
| **`WithLimit(n int)`** | Sets the universal `limit` parameter (values ≤ 0 ignored). |
| **`WithOffset(n int)`** | Sets the universal `offset` parameter for pagination (values ≤ 0 ignored). |
| **`WithDateFormat(format string)`** | Sets the universal `dateformat` parameter. Advanced use — see [Parameters](https://www.marketdata.app/docs/sdk/go/parameters). |
| **`WithHumanReadable(enabled bool)`** | Sets the universal `human` parameter. Advanced use — see [Parameters](https://www.marketdata.app/docs/sdk/go/parameters). |
| **`WithAddHeaders(enabled bool)`** | Sets the universal `headers` parameter (affects CSV output only). |
The universal-parameter options (`WithMode`, `WithMaxAge`, `WithColumns`, `WithLimit`, `WithOffset`, `WithDateFormat`, `WithHumanReadable`, `WithAddHeaders`) are documented in depth on the [Parameters](https://www.marketdata.app/docs/sdk/go/parameters) page.
> [!NOTE]
> **[Fixed timeouts and retry behavior]**
>
> Per-request timeout (99s) and connection dial timeout (2s) are **fixed and cannot be changed**. Only the retry *count* is configurable, via `WithMaxRetries`. Requests are retried only on 501–599 status codes and transient network errors, with exponential backoff starting at 1s and doubling each attempt.
### Example: a fully configured client
```go
package main
import (
"log"
"log/slog"
"os"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
client, err := marketdata.NewClient(
marketdata.WithToken("your-token"),
marketdata.WithEnvironment(marketdata.Production),
marketdata.WithLogger(logger),
marketdata.WithMaxRetries(5),
marketdata.WithMode(marketdata.ModeCached),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
}
```
## Resource Services
The client exposes five services, one per API resource:
| Field | Type | Description |
|--------------------|----------------------|------------------------------------------------------------------|
| `client.Stocks` | `*stocks.Service` | Stock quotes, candles, bulk candles, prices, earnings, and news. |
| `client.Options` | `*options.Service` | Option chains, expirations, quotes, and symbol lookup. |
| `client.Funds` | `*funds.Service` | Mutual fund candles. |
| `client.Markets` | `*markets.Service` | Market status (open/closed) and status history. |
| `client.Utilities` | `*utilities.Service` | API status, response-header echo, and account/user details. |
```go
quote, err := client.Stocks.GetQuote("AAPL")
chain, err := client.Options.GetChain("AAPL")
status, err := client.Markets.GetStatus()
user, err := client.Utilities.GetUser()
```
## Two Call Styles
Every endpoint method comes in two forms. Choose based on whether you need a `context.Context` and the raw response metadata.
| Method form | Signature shape | When to use |
|------------------------|------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| **`Get(...)`** | `(T, error)` | Convenience form. Uses a background context. Best for scripts and simple calls. |
| **`(ctx, ...)`** | `(T, *marketdata.Response, error)` | Context-aware form. Also returns the raw `*marketdata.Response` carrying HTTP status, headers, and per-request rate-limit metadata. |
The `*marketdata.Response` returned by the context form embeds `*http.Response` and adds a `NoData` flag and a `RateLimit` field. (`marketdata.Response` is an exported alias for the SDK's response type, so you can name it in your own code.)
### Get (simple)
```go
package main
import (
"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.GetQuote("AAPL")
if err != nil {
log.Fatal(err)
}
fmt.Printf("AAPL last: $%.2f\n", quote.Last)
}
```
### Context + Response
```go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient()
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
quote, resp, err := client.Stocks.Quote(ctx, "AAPL")
if err != nil {
log.Fatal(err)
}
fmt.Printf("AAPL last: $%.2f\n", quote.Last)
fmt.Printf("HTTP status: %s\n", resp.Status)
fmt.Printf("Credits remaining: %d\n", resp.RateLimit.Remaining)
fmt.Printf("No data: %t\n", resp.NoData)
}
```
## No-Data Responses
A `404` (no data available for a valid request) or a `204` (a `mode=cached` cache miss) is **not** an error. The context form returns a nil result, a nil error, and a `*marketdata.Response` whose `NoData` field is `true`. Always check `NoData` (or a nil result) before dereferencing:
```go
quote, resp, err := client.Stocks.Quote(ctx, "AAPL")
if err != nil {
log.Fatal(err)
}
if resp.NoData {
fmt.Println("no quote data available")
return
}
fmt.Printf("AAPL last: $%.2f\n", quote.Last)
```
## Client Methods
Besides the resource services, the client provides a few helper methods:
| Method | Description |
|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`Close() error`** | Releases resources by closing idle HTTP connections. Safe to call multiple times; always returns `nil`. Do not reuse a client after `Close`. |
| **`RateLimits() RateLimitState`** | Returns a client-level snapshot of rate-limit state from the most recently completed request. For exact, request-scoped values, read `resp.RateLimit` instead. |
| **`DemoMode() bool`** | Reports whether the client is running in demo mode because no token was provided. |
| **`Debug(enabled bool)`** | Turns debug logging on or off at runtime (equivalent to `WithDebug`). |
```go
if client.DemoMode() {
fmt.Println("running with limited demo access")
}
rl := client.RateLimits()
fmt.Printf("credits: %d/%d, resets %s\n", rl.Remaining, rl.Limit, rl.ResetAt)
```
## Next Steps
- Configure [Parameters](https://www.marketdata.app/docs/sdk/go/parameters) to control mode, columns, and pagination.
- Learn the [Error](https://www.marketdata.app/docs/sdk/go/errors) types and how to inspect them.
- Set up [Logging](https://www.marketdata.app/docs/sdk/go/logging) for debugging.
---
# Error Handling (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/errors/index.md
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`:
```go
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:
```go
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](https://www.marketdata.app/dashboard/).
## 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.
> [!NOTE]
> **[404 is not always an error]**
>
> 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`](https://www.marketdata.app/docs/sdk/go/client#no-data-responses). 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 a `WaitDuration()` method returning how long to wait before retrying.
- **`ForbiddenError`** — `AuthorizedIP`, `BlockedIP`, and `TroubleshootingGuide` (a 403 usually means your IP changed and access was temporarily blocked).
- **`ValidationError`** — `Field` and `Message` identify the offending input. Because no request was made, its `SupportInfo()` 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 a `Message`.
## Classifying Errors
Use `errors.As` when you need a typed error's fields, and `errors.Is` when you only need to know the category.
```go
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:
```go
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:
```go
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:
```go
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
--------------------------------
```
## Next Steps
- Review how [Logging](https://www.marketdata.app/docs/sdk/go/logging) records errors and redacts your token.
- See the [Client](https://www.marketdata.app/docs/sdk/go/client) page for how no-data responses differ from errors.
---
# Funds (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/funds/index.md
Access historical net asset value (NAV) candles for mutual funds. The Go SDK exposes these endpoints through the `client.Funds` service.
## Mutual Funds Endpoints
---
# Fund Candles (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/funds/candles/index.md
Retrieve historical OHLC candles for a mutual fund. Each candle reports the fund's net asset value (NAV) for one period, with its `Time` normalized to US Eastern.
## Making Requests
Fetch fund candles with the `Candles` method on the `client.Funds` service. It comes in two call styles:
| Method | Return | Description |
|-------------------------------------|-----------------------------------------------|------------------------------------------------------------------------|
| **`GetCandles(symbol, ...opts)`** | `([]funds.Candle, error)` | Convenience; uses a background context and discards response metadata. |
| **`Candles(ctx, symbol, ...opts)`** | `([]funds.Candle, *response.Response, error)` | Context-aware; also returns the raw response plus rate-limit metadata. |
## Candles
```go
func (s *Service) Candles(ctx context.Context, symbol string, opts ...funds.CandleOption) ([]funds.Candle, *response.Response, error)
func (s *Service) GetCandles(symbol string, opts ...funds.CandleOption) ([]funds.Candle, error)
```
Get historical OHLC candles for a mutual fund from the `/v1/funds/candles/{resolution}/{symbol}/` endpoint. Dates are sent to the API in `YYYY-MM-DD` form, so any time-of-day component is ignored.
### Parameters
- `ctx` (`context.Context`) — request context for cancellation and deadlines (context method only).
- `symbol` (`string`) — the fund's ticker, for example `"VFINX"`. Required; an empty symbol returns a validation error without making a request.
- Options:
- `funds.WithResolution(r funds.Resolution)` — the candle timeframe. Defaults to `funds.ResolutionDaily`. The funds endpoint supports `funds.ResolutionDaily` (`"D"`), `funds.ResolutionWeekly` (`"W"`), `funds.ResolutionMonthly` (`"M"`), and `funds.ResolutionYearly` (`"Y"`) only. The resolution becomes part of the request path, so an unsupported value causes the API to reject the request.
- `funds.WithCandleWindow(w funds.DateWindow)` — the date range, expressed as a single `funds.DateWindow` value. Omitting it lets the API return its default recent window.
### Returns
- `[]funds.Candle` — one [`Candle`](#candle) per period, oldest first.
- `*response.Response` — raw response plus rate-limit metadata (context method only).
- `error` — non-nil on request or decoding failure. A missing symbol returns a `*marketdata.ValidationError`.
### Notes
- Mutual funds price once per day at market close, so daily resolution is the most common choice.
- When the API responds `404` because no data exists for the requested symbol or range, both call styles return a `nil` slice and a `nil` error. With the context method, the returned `*response.Response` has its `NoData` field set to `true`.
- The date parameters (`date`, `from`, `to`, `countback`) are mutually exclusive by construction: a `DateWindow` is a single sealed value, so an illegal pairing such as "from plus countback" cannot be expressed.
### Get (simple)
```go
package main
import (
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
"github.com/MarketDataApp/sdk-go/v2/marketdata/funds"
)
func main() {
client, err := marketdata.NewClient() // reads MARKETDATA_TOKEN from env / .env
if err != nil {
log.Fatal(err)
}
defer client.Close()
// The 5 most recent daily candles for the Vanguard 500 Index Fund.
candles, err := client.Funds.GetCandles("VFINX",
funds.WithCandleWindow(funds.LastN(5)),
)
if err != nil {
log.Fatal(err)
}
for _, candle := range candles {
fmt.Println(candle)
}
}
```
**Output**
```
2023-01-03 O: 352.76 H: 352.76 L: 352.76 C: 352.76
2023-01-04 O: 355.43 H: 355.43 L: 355.43 C: 355.43
2023-01-05 O: 351.35 H: 351.35 L: 351.35 C: 351.35
2023-01-06 O: 359.38 H: 359.38 L: 359.38 C: 359.38
```
### Context + Response
```go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
"github.com/MarketDataApp/sdk-go/v2/marketdata/funds"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
from := time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC)
to := time.Date(2023, time.January, 6, 0, 0, 0, 0, time.UTC)
candles, resp, err := client.Funds.Candles(context.Background(), "VFINX",
funds.WithResolution(funds.ResolutionDaily),
funds.WithCandleWindow(funds.Between(from, to)),
)
if err != nil {
log.Fatal(err)
}
if resp.NoData {
fmt.Println("No data for the requested range.")
return
}
for _, candle := range candles {
fmt.Println(candle)
}
// The Response exposes request-scoped rate-limit metadata.
fmt.Printf("Credits remaining: %d\n", resp.RateLimit.Remaining)
}
```
**Output**
```
2023-01-03 O: 352.76 H: 352.76 L: 352.76 C: 352.76
2023-01-04 O: 355.43 H: 355.43 L: 355.43 C: 355.43
2023-01-05 O: 351.35 H: 351.35 L: 351.35 C: 351.35
2023-01-06 O: 359.38 H: 359.38 L: 359.38 C: 359.38
Credits remaining: 99847
```
### Date windows
```go
// Every DateWindow constructor is a sealed value; pass exactly one to WithCandleWindow.
funds.OnDate(day) // a single calendar day -> date=day
funds.Between(from, to) // an explicit closed range -> from=from&to=to
funds.Since(from) // everything since a day -> from=from
funds.Until(to) // up to and including a day -> to=to
funds.LastN(n) // the n most recent periods -> countback=n
funds.LastNUntil(n, to) // the n periods ending at a day -> countback=n&to=to
// Example: the 12 most recent monthly candles.
candles, err := client.Funds.GetCandles("VFINX",
funds.WithResolution(funds.ResolutionMonthly),
funds.WithCandleWindow(funds.LastN(12)),
)
```
## Candle
```go
type Candle struct {
Time time.Time `json:"t"` // Time is the candle timestamp (US Eastern).
Open float64 `json:"o"` // Open is the opening price (NAV).
High float64 `json:"h"` // High is the highest price.
Low float64 `json:"l"` // Low is the lowest price.
Close float64 `json:"c"` // Close is the closing price (NAV).
}
```
`Candle` represents an OHLC candle for a fund. Because mutual funds report a single NAV per period, the open, high, low, and close are typically identical.
### Fields
- `Time` (`time.Time`) — the candle timestamp, normalized to US Eastern.
- `Open` (`float64`) — the opening NAV.
- `High` (`float64`) — the highest price during the period.
- `Low` (`float64`) — the lowest price during the period.
- `Close` (`float64`) — the closing NAV.
### Methods
- `String() string` — a concise one-line summary, e.g. `2023-01-03 O: 352.76 H: 352.76 L: 352.76 C: 352.76`.
- `Range() float64` — the high-low range (`High - Low`).
> [!NOTE]
> **[Zero values and null data]**
>
> Numeric fields are value types, not pointers. When the API returns `null` for a field, it decodes as the zero value, so a `0` may represent either an actual zero or the absence of data.
## Resolution
```go
type Resolution string
const (
ResolutionDaily Resolution = "D"
ResolutionWeekly Resolution = "W"
ResolutionMonthly Resolution = "M"
ResolutionYearly Resolution = "Y"
)
```
`Resolution` selects the candle timeframe passed to [`WithResolution`](#candles). The funds candles endpoint supports daily, weekly, monthly, and yearly only (quarterly is not accepted). `Resolution` has a `String()` method that returns the underlying code.
---
# Installation (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/installation/index.md
The Market Data Go SDK is installed with the standard `go get` command. It is a normal Go module with a `/v2` major-version suffix on its import path.
## Requirements
- **Go 1.22 or later** (the module declares `go 1.22.5`)
- No third-party runtime dependencies — the SDK is built entirely on the Go standard library
## Installing with `go get`
From inside your Go module, run:
```bash
go get github.com/MarketDataApp/sdk-go/v2
```
This adds the SDK to your `go.mod` and downloads it into the module cache.
> [!NOTE]
> **[Version suffix matters]**
>
> Because this is a `/v2` module, the import path **must** include `/v2`. Dropping it would resolve to the older v1 SDK, which has a different (fluent-builder) API.
## Import Paths
The public API lives in the `marketdata` package, and each resource has its own sub-package for its functional options and helper constructors:
```go
import (
"github.com/MarketDataApp/sdk-go/v2/marketdata"
// Import a resource package only when you use its options or helpers:
"github.com/MarketDataApp/sdk-go/v2/marketdata/stocks"
"github.com/MarketDataApp/sdk-go/v2/marketdata/options"
"github.com/MarketDataApp/sdk-go/v2/marketdata/funds"
"github.com/MarketDataApp/sdk-go/v2/marketdata/markets"
"github.com/MarketDataApp/sdk-go/v2/marketdata/utilities"
)
```
You always import `marketdata` (for `NewClient` and the client-level options). You only need a resource package such as `stocks` when you pass one of its functional options, for example `stocks.WithResolution(...)`.
## Verifying the Installation
Create a small program to confirm everything compiles and the SDK can reach the API. The `/status/` endpoint is public, so this works even before you configure a token:
```go
package main
import (
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient()
if err != nil {
log.Fatal(err)
}
defer client.Close()
status, err := client.Utilities.GetStatus()
if err != nil {
log.Fatal(err)
}
fmt.Println(status)
}
```
Run it:
```bash
go run .
```
```
API online (30d: 99.99% 90d: 99.98%) Updated: 2026-07-12 09:30:00
```
If you see the status line, the SDK is installed correctly.
## Updating the SDK
To move to the latest release:
```bash
go get -u github.com/MarketDataApp/sdk-go/v2
go mod tidy
```
## Next Steps
After installing, [set up your authentication token](https://www.marketdata.app/docs/sdk/go/authentication) so you can call the endpoints that require a paid or trial plan.
---
# Logging (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/logging/index.md
The Go SDK logs through the standard library's [`log/slog`](https://pkg.go.dev/log/slog) package. By default it uses `slog.Default()`, so SDK log records flow into whatever logger your application already configures. You can supply your own `*slog.Logger`, raise the verbosity for debugging, and rely on the SDK never printing your full API token.
## Default Behavior
Out of the box the SDK logs at `slog.Default()`'s level. Setting the `MARKETDATA_LOGGING_LEVEL` environment variable makes the SDK install its own text handler (writing to stderr) at the level you choose:
```bash
export MARKETDATA_LOGGING_LEVEL=DEBUG
```
Recognized values (case-insensitive): `DEBUG`, `INFO`, `WARNING` (or `WARN`), and `ERROR`. Setting it to `DEBUG` also turns on the SDK's verbose request/response debug logging automatically.
## Enabling Debug Logging
In code, enable verbose logging with the `WithDebug` option, or toggle it at runtime with `client.Debug(...)`:
```go
package main
import (
"log/slog"
"os"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
client, err := marketdata.NewClient(
marketdata.WithToken("your-token"),
marketdata.WithLogger(logger),
marketdata.WithDebug(true),
)
if err != nil {
logger.Error("client init failed", "err", err)
os.Exit(1)
}
defer client.Close()
// Turn debug logging off once you are past the section you are diagnosing.
client.Debug(false)
}
```
- **`WithLogger(l *slog.Logger)`** replaces `slog.Default()` for this client.
- **`WithDebug(true)`** enables verbose request/response logging.
- **`client.Debug(enabled bool)`** toggles debug logging at runtime.
## Log Levels
The SDK emits records at standard `slog` levels:
| Level | What the SDK logs |
|-------------------|------------------------------------------------------------------------------------------|
| `slog.LevelDebug` | Request URLs and parameters, response status, retry attempts, the redacted token in use. |
| `slog.LevelInfo` | Normal operational messages. |
| `slog.LevelWarn` | Demo-mode startup warning; transient-failure retries. |
| `slog.LevelError` | Errors surfaced by the SDK. |
## Choosing a Handler
Because the SDK uses `slog`, you have full control over formatting and destination through the handler you build.
### Text (stderr)
```go
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
client, err := marketdata.NewClient(marketdata.WithLogger(logger))
```
### JSON (structured)
```go
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
client, err := marketdata.NewClient(marketdata.WithLogger(logger))
```
## Token Redaction
The SDK **never logs your full API token**. When debug logging is enabled and a token is present, the SDK logs the token redacted to its last four characters, with everything before them replaced by asterisks:
```
level=DEBUG msg="using API token" token=****************************abcd
```
This redaction applies everywhere the token would otherwise appear in output, so debug logs are safe to share in a support ticket. (The SDK additionally refuses to send the token at all over a non-HTTPS connection to a non-loopback host.)
## Disabling Logging
To silence the SDK, pass a logger whose handler discards records or sits at a high level:
```go
// Discard everything.
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
client, err := marketdata.NewClient(marketdata.WithLogger(logger))
```
## Production Recommendations
- Use `slog.LevelWarn` or `slog.LevelError` in production to keep log volume down.
- Reserve `slog.LevelDebug` for local development or active troubleshooting.
- Prefer the JSON handler when shipping logs to an aggregation system.
## Next Steps
- See [Error Handling](https://www.marketdata.app/docs/sdk/go/errors) for the structured error context that appears in error logs.
- Review the [Client](https://www.marketdata.app/docs/sdk/go/client) options, including `WithLogger` and `WithDebug`.
---
# Markets (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/markets/index.md
Check whether a market was open, closed, or closed early on any trading day. The Go SDK exposes these endpoints through the `client.Markets` service.
## Markets Endpoints
---
# Status History
Source: https://www.marketdata.app/docs/sdk/go/markets/status-history/index.md
Retrieve the market status for each day in a date range, returning one entry per day.
## Making Requests
Fetch a range of days with the `StatusHistory` method on the `client.Markets` service.
| Method | Return | Description |
|-----------------------------------|-------------------------------------------------------|-----------------------------------------------------------------------------------------|
| **`StatusHistory(ctx, ...opts)`** | `([]markets.MarketStatus, *response.Response, error)` | Context-aware; returns one entry per day plus the raw response and rate-limit metadata. |
> [!NOTE]
> **[Context method only]**
>
> Unlike most SDK endpoints, `StatusHistory` has no `Get*` convenience wrapper — it is only available as a context-aware method. For a single day, use [Status](https://www.marketdata.app/docs/sdk/go/markets/status), which offers both call styles.
## StatusHistory
```go
func (s *Service) StatusHistory(ctx context.Context, opts ...markets.HistoryOption) ([]markets.MarketStatus, *response.Response, error)
```
Get the market status for a range of days from the `/v1/markets/status/` endpoint, returning one [`MarketStatus`](https://www.marketdata.app/docs/sdk/go/markets/status#marketstatus) per day. Dates are sent to the API in `YYYY-MM-DD` form, so any time-of-day component is ignored.
### Parameters
- `ctx` (`context.Context`) — request context for cancellation and deadlines.
- Options:
- `markets.WithHistoryWindow(w markets.HistoryWindow)` — the date range, expressed as a single [`HistoryWindow`](#historywindow) value. Omitting it lets the API return its default recent range. This is a `StatusHistory`-only option; it cannot be passed to `Status`.
- `markets.WithCountry(country string)` — the market to query, as a two-letter ISO 3166-1 alpha-2 code such as `"US"`. An empty string defaults to the United States. `WithCountry` applies to both `Status` and `StatusHistory`.
### Returns
- `[]markets.MarketStatus` — one [`MarketStatus`](https://www.marketdata.app/docs/sdk/go/markets/status#marketstatus) per day in the range, oldest first.
- `*response.Response` — raw response plus rate-limit metadata.
- `error` — non-nil on request or decoding failure.
### Notes
- When the API responds `404` because no data exists for the requested range, `StatusHistory` returns a `nil` slice and a `nil` error; the returned `*response.Response` has its `NoData` field set to `true`.
- The range parameters (`from`, `to`, `countback`) are mutually exclusive by construction: a `HistoryWindow` is a single sealed value, so an illegal pairing such as "from plus countback" cannot be expressed. There is deliberately no single-date mode — a single calendar day is a [Status](https://www.marketdata.app/docs/sdk/go/markets/status) concept, selected with `WithDate`.
### Context + Response
```go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
"github.com/MarketDataApp/sdk-go/v2/marketdata/markets"
)
func main() {
client, err := marketdata.NewClient() // reads MARKETDATA_TOKEN from env / .env
if err != nil {
log.Fatal(err)
}
defer client.Close()
from := time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC)
to := time.Date(2024, time.January, 10, 0, 0, 0, 0, time.UTC)
statuses, resp, err := client.Markets.StatusHistory(context.Background(),
markets.WithHistoryWindow(markets.Between(from, to)),
markets.WithCountry("US"),
)
if err != nil {
log.Fatal(err)
}
if resp.NoData {
fmt.Println("No status history for the requested range.")
return
}
for _, status := range statuses {
mark := "closed"
if status.Open {
mark = "open"
}
fmt.Printf("%s: %s\n", status.Date.Format("2006-01-02"), mark)
}
}
```
**Output**
```
2024-01-01: closed
2024-01-02: open
2024-01-03: open
2024-01-04: open
2024-01-05: open
2024-01-06: closed
2024-01-07: closed
2024-01-08: open
2024-01-09: open
2024-01-10: open
```
### History windows
```go
// Every HistoryWindow constructor is a sealed value; pass exactly one to WithHistoryWindow.
// Note: there is no OnDate mode here — a single day is a Status concept (use markets.WithDate).
markets.Between(from, to) // an explicit closed range -> from=from&to=to
markets.Since(from) // everything since a day -> from=from
markets.Until(to) // up to and including a day -> to=to
markets.LastN(n) // the n most recent days -> countback=n
markets.LastNUntil(n, to) // the n days ending at a day -> countback=n&to=to
// Example: the last 5 trading-calendar days.
statuses, _, err := client.Markets.StatusHistory(context.Background(),
markets.WithHistoryWindow(markets.LastN(5)),
)
```
## HistoryWindow
```go
type HistoryWindow interface {
// contains filtered or unexported methods
}
```
`HistoryWindow` selects the date range for `StatusHistory`. It is a sealed union: build it with exactly one of the constructors below. Because a `HistoryWindow` is a single value, the API's mutually-exclusive range parameters can never be combined by mistake. Only the calendar date of each `time.Time` is used; the time-of-day and zone are ignored.
- `markets.Between(from, to time.Time) HistoryWindow` — an explicit closed range from `from` to `to` (inclusive).
- `markets.Since(from time.Time) HistoryWindow` — everything from the given day onward.
- `markets.Until(to time.Time) HistoryWindow` — data up to and including the given day.
- `markets.LastN(n int) HistoryWindow` — the `n` most recent days (the API's `countback`).
- `markets.LastNUntil(n int, to time.Time) HistoryWindow` — the `n` days ending at the given day.
The returned entries use the same [`MarketStatus`](https://www.marketdata.app/docs/sdk/go/markets/status#marketstatus) type documented on the Status page.
---
# Market Status (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/markets/status/index.md
Retrieve the market status (open, closed, or early close) for a single trading day — past, present, or future.
## Making Requests
Fetch a single day's status with the `Status` method on the `client.Markets` service. It comes in two call styles:
| Method | Return | Description |
|----------------------------|------------------------------------------------------|------------------------------------------------------------------------|
| **`GetStatus(...opts)`** | `(*markets.MarketStatus, error)` | Convenience; uses a background context and discards response metadata. |
| **`Status(ctx, ...opts)`** | `(*markets.MarketStatus, *response.Response, error)` | Context-aware; also returns the raw response plus rate-limit metadata. |
For a range of days, use [Status History](https://www.marketdata.app/docs/sdk/go/markets/status-history) instead.
## Status
```go
func (s *Service) Status(ctx context.Context, opts ...markets.StatusOption) (*markets.MarketStatus, *response.Response, error)
func (s *Service) GetStatus(opts ...markets.StatusOption) (*markets.MarketStatus, error)
```
Get the market status for a single day from the `/v1/markets/status/` endpoint. With no options it reports today's status for the United States. The date is sent to the API in `YYYY-MM-DD` form, so any time-of-day component is ignored.
### Parameters
- `ctx` (`context.Context`) — request context for cancellation and deadlines (context method only).
- Options:
- `markets.WithDate(d time.Time)` — the day whose status is requested. A zero time leaves the parameter unset and the API reports today's status. This is a `Status`-only option; it cannot be passed to `StatusHistory`.
- `markets.WithCountry(country string)` — the market to query, as a two-letter ISO 3166-1 alpha-2 code such as `"US"`. An empty string defaults to the United States. `WithCountry` applies to both `Status` and `StatusHistory`.
### Returns
- `*markets.MarketStatus` — the [`MarketStatus`](#marketstatus) for the requested day.
- `*response.Response` — raw response plus rate-limit metadata (context method only).
- `error` — non-nil on request or decoding failure.
### Notes
- When the API responds `404` because no status exists for the requested date, both call styles return a `nil` `MarketStatus` and a `nil` error. With the context method, the returned `*response.Response` has its `NoData` field set to `true` — always guard against a `nil` result before dereferencing it.
- `Status` accepts only single-day options. Its date parameter is a single `time.Time`, so the range parameters used by `StatusHistory` cannot be passed here by mistake.
### Get (simple)
```go
package main
import (
"fmt"
"log"
"time"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
"github.com/MarketDataApp/sdk-go/v2/marketdata/markets"
)
func main() {
client, err := marketdata.NewClient() // reads MARKETDATA_TOKEN from env / .env
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Was the US market open on Independence Day 2024?
status, err := client.Markets.GetStatus(
markets.WithDate(time.Date(2024, time.July, 4, 0, 0, 0, 0, time.UTC)),
)
if err != nil {
log.Fatal(err)
}
if status == nil {
fmt.Println("No status available for that date.")
return
}
fmt.Println(status)
}
```
**Output**
```
2024-07-04 closed Open: false
```
### Context + Response
```go
package main
import (
"context"
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
// No options: today's status for the United States.
status, resp, err := client.Markets.Status(context.Background())
if err != nil {
log.Fatal(err)
}
if resp.NoData || status == nil {
fmt.Println("No status available.")
return
}
if status.Open {
fmt.Println("The market is open.")
} else {
fmt.Println("The market is closed.")
}
fmt.Printf("Credits remaining: %d\n", resp.RateLimit.Remaining)
}
```
**Output**
```
The market is open.
Credits remaining: 99846
```
### With options
```go
// Query a specific market on a specific day.
status, _, err := client.Markets.Status(context.Background(),
markets.WithCountry("US"),
markets.WithDate(time.Date(2024, time.November, 28, 0, 0, 0, 0, time.UTC)),
)
if err != nil {
log.Fatal(err)
}
if status != nil {
fmt.Printf("early close: %t\n", status.IsEarlyClose())
}
```
## MarketStatus
```go
type MarketStatus struct {
Date time.Time `json:"date"` // Date is the date of this status (US Eastern).
Open bool `json:"open"` // Open indicates if the market was/is open on this date.
Status string `json:"status"` // Status is a string description.
}
```
`MarketStatus` represents the status of the market on a given day. `Status` is the raw string from the API (`"open"`, `"closed"`, or `"early-close"`), and `Open` is derived from it.
### Fields
- `Date` (`time.Time`) — the date of this status, normalized to US Eastern.
- `Open` (`bool`) — `true` when the market was or is open on this date.
- `Status` (`string`) — a description: `"open"`, `"closed"`, or `"early-close"`.
### Methods
- `String() string` — a one-line summary, e.g. `2024-07-04 closed Open: false`.
- `IsOpen() bool` — reports whether the market is open.
- `IsClosed() bool` — reports whether the market is closed.
- `IsEarlyClose() bool` — reports whether this is an early-close day (`Status == "early-close"`).
---
# Options (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/options/index.md
Access options market data with the Go SDK: full option chains, expiration
dates, single and multi-contract quotes, and OCC option symbol lookup. Each
endpoint exposes a context-aware method that returns typed data plus a
`*response.Response` (raw body and rate-limit metadata) and a convenience
`Get*` wrapper that uses a background context.
## Options Endpoints
---
# Option Chain (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/options/chain/index.md
Fetch a current or historical option chain for an underlying symbol, with a
rich set of composable filters for expiration, strike, side, moneyness, and
liquidity.
## Making Requests
`Chain` returns an `*OptionsChain` holding one `OptionQuote` per contract. The
symbol is required; every filter is an optional `ChainOption`.
Choose the call style that fits your code:
| Method | Return | Description |
|-----------------------------------|----------------------------------------------|-----------------------------------------------------------------------|
| **`GetChain(symbol, ...opts)`** | `(*OptionsChain, error)` | Convenience; uses a background context. |
| **`Chain(ctx, symbol, ...opts)`** | `(*OptionsChain, *response.Response, error)` | Context-aware; also returns the raw response and rate-limit metadata. |
## Chain
```go
func (s *Service) Chain(ctx context.Context, symbol string, opts ...ChainOption) (*OptionsChain, *response.Response, error)
func (s *Service) GetChain(symbol string, opts ...ChainOption) (*OptionsChain, error)
```
`Chain` fetches the options chain for an underlying stock symbol. Without any
options the API returns every active contract across all expirations, so most
callers narrow the result with filters. The two groups of mutually-exclusive
API parameters — contract selection and expiration selection — are each
collapsed into a single sealed-union option ([`WithStrike`](#strike-filters)
and [`WithExpiry`](#expiration-filters)), so the API's silently-conflicting
combinations cannot be written.
### Parameters
- `symbol` (`string`) — the underlying stock or index symbol (for example `"AAPL"`, `"SPY"`, `"SPX"`). Required; an empty string is rejected with a `marketdata.ValidationError` before any request is made.
#### Expiration filters
- `options.WithExpiry(f ExpiryFilter)` — sets the expiration selector from a single `ExpiryFilter`. Replaces the API's mutually-exclusive `expiration`, `dte`, `month`, and `year` parameters with one value so they can never conflict. Build the filter with exactly one of:
- `options.OnExpiration(t time.Time)` — a single expiration date (`expiration=YYYY-MM-DD`). Only the calendar date is used; must not be zero.
- `options.InDTE(days int)` — the expiration closest to `days` days to expiry, counted from today (`dte=days`). `days` must be zero or greater.
- `options.InMonth(month int)` — contracts expiring in the given calendar month across all years (`month=month`); `month` is 1 through 12.
- `options.InYear(year int)` — contracts expiring in the given four-digit year across all months (`year=year`); `year` is at least 1900.
- `options.InMonthOfYear(month, year int)` — a specific month of a specific year (`month=month&year=year`).
- `options.ExpirationBetween(from, to time.Time)` — contracts expiring within an inclusive date range (`from=YYYY-MM-DD&to=YYYY-MM-DD`). Both dates are required and `from` must not be after `to`. This filters which expirations are returned, so it is an expiry mode, not a historical snapshot.
- `options.WithChainDate(t time.Time)` — request the chain as it stood on a single historical trading day (`date=YYYY-MM-DD`). Only the calendar date is used; a zero time returns the current chain. Independent of the expiration selector, so a historical chain can still be narrowed by expiration or strike.
#### Strike filters
- `options.WithStrike(f StrikeFilter)` — sets the contract selector from a single `StrikeFilter`. Replaces the API's mutually-exclusive `strike` and `delta` parameters with one value. Build the filter with exactly one of:
- `options.Strike(x float64)` — an exact strike (`strike=x`); `x` must be greater than zero.
- `options.StrikeRange(lo, hi float64)` — an inclusive strike range (`strike=lo-hi`); both bounds greater than zero and `lo` must not exceed `hi`.
- `options.MinStrike(x float64)` — strikes at or above `x` (`strike=>=x`).
- `options.MaxStrike(x float64)` — strikes at or below `x` (`strike=<=x`).
- `options.StrikeExpr(expr string)` — the API's raw strike expression, passed verbatim (`strike=expr`), such as `"150"`, `"140-160"`, `">=140"`, or `"<=160"`. An escape hatch for expressions the typed constructors do not cover; must not be empty.
- `options.ByDelta(d float64)` — the strikes nearest delta `d` (`delta=d`). The API filters on the **absolute value** of delta and always returns **both sides** (calls and puts), so `ByDelta` does not choose a side — combine it with `WithSide` to keep only calls or puts. `d` must be non-zero and within `[-1, 1]` (puts have negative delta; a negative value behaves the same as its positive counterpart).
- `options.WithStrikeLimit(limit int)` — cap the number of strikes returned, excluding the strikes furthest from the money. Values less than or equal to zero leave the limit unset.
#### Side, moneyness, and contract type
- `options.WithSide(side OptionSide)` — limit the chain to one side: `options.SideCall` (calls only) or `options.SidePut` (puts only). `options.SideBoth` (the default) leaves the parameter unset and returns both sides.
- `options.WithRange(r string)` — filter by moneyness using the API's range keywords, such as `"itm"` (in the money), `"otm"` (out of the money), or `"all"`. An empty string leaves the filter unset.
- `options.WithNonstandard(nonstandard bool)` — include (`true`) or exclude (`false`) nonstandard contracts, such as adjusted contracts created by splits or mergers. When the option is not used, the API default applies.
- `options.WithExpirationTypes(f ExpirationTypeFilter)` — include or exclude expiration cadences via a single `ExpirationTypeFilter`. Build it with exactly one of:
- `options.IncludeExpirationTypes(types ...ExpirationType)` — limit the chain to only the given cadences (for example `weekly=true&monthly=true`).
- `options.ExcludeExpirationTypes(types ...ExpirationType)` — exclude the given cadences (for example `quarterly=false`).
The cadence constants are `options.Weekly`, `options.Monthly`, and `options.Quarterly`. The API forbids mixing inclusion and exclusion in one request; because the choice is a single value, that illegal mix cannot be expressed.
#### Settlement (index options only)
- `options.WithAM(am bool)` — limit the chain to AM-settled contracts (`true`) or exclude them (`false`). Settlement style is meaningful only for **index options** (for example SPX, NDX); on single-stock and ETF options the API tolerates the parameter but it has no effect. When the option is not used, settlement is not filtered.
- `options.WithPM(pm bool)` — the PM-settled counterpart of `WithAM`, with the same index-only semantics.
#### Price and liquidity filters
- `options.WithMinBid(min float64)` / `options.WithMaxBid(max float64)` — limit the chain to contracts whose bid price is at least `min` / at most `max`.
- `options.WithMinAsk(min float64)` / `options.WithMaxAsk(max float64)` — limit the chain to contracts whose ask price is at least `min` / at most `max`.
- `options.WithMaxBidAskSpread(max float64)` — maximum bid-ask spread in dollars.
- `options.WithMaxBidAskSpreadPct(max float64)` — maximum bid-ask spread as a percentage of the midpoint price.
- `options.WithMinOpenInterest(min int)` — minimum open interest, filtering out thinly held contracts.
- `options.WithMinVolume(min int)` — minimum trading volume, filtering out thinly traded contracts.
### Returns
- `*OptionsChain` — the chain and its contracts. `nil` when there is no data (see Notes).
- `*response.Response` — the raw response plus rate-limit metadata (context method only).
- `error` — non-nil on a validation failure, transport error, or unexpected API status.
### Notes
- **Mutually-exclusive selectors are compile-time exclusive by design.** `WithStrike` and `WithExpiry` each take a single sealed-union value, so the API's silently-conflicting combinations (strike vs. delta; expiration vs. dte/month/year) cannot be written. Delta is a **mode of the strike filter** (`options.ByDelta`), not a separate option, because the API silently honors strike over delta when both are sent.
- **Include vs. exclude expiration types is also compile-time exclusive.** A single `ExpirationTypeFilter` is either an include set or an exclude set, so the API's forbidden `weekly=true&monthly=false` mix cannot be produced.
- **`am`/`pm` are index-only.** They filter settlement style only for index options; on stocks and ETFs the API tolerates them without effect.
- **Historical snapshot vs. expiration range.** `WithChainDate` requests the chain *as of* a single day and is independent of the expiration selector. A `from`/`to` date range filters *expirations*, not history — use `options.ExpirationBetween` via `WithExpiry` for that.
- **Validation happens before the network call.** A strike or delta out of range, a month outside 1..12, a year before 1900, or a malformed date range is rejected with a `marketdata.ValidationError` before any request is made.
- **No-data behavior.** If the API has no data for the request (HTTP 404), `Chain` returns a `nil` chain, a response whose `NoData` field is `true`, and a `nil` error. Check for a `nil` chain before use.
- Requesting a full chain for a large underlying (SPX, SPY, QQQ) can return tens of thousands of contracts; narrow with filters to control credit usage.
### Get (simple)
```go
package main
import (
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
"github.com/MarketDataApp/sdk-go/v2/marketdata/options"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Calls expiring nearest 45 days out.
chain, err := client.Options.GetChain("AAPL",
options.WithExpiry(options.InDTE(45)),
options.WithSide(options.SideCall),
)
if err != nil {
log.Fatal(err)
}
if chain == nil {
fmt.Println("No data for the requested chain.")
return
}
fmt.Printf("%s: %d contracts\n", chain.Underlying, len(chain.Options))
for _, o := range chain.Options {
fmt.Printf("%s $%.2f Bid: %.2f Ask: %.2f OI: %d\n",
o.OptionSymbol, o.Strike, o.Bid, o.Ask, o.OpenInterest)
}
}
```
**Output**
```
AAPL: 3 contracts
AAPL271217C00280000 $180.00 Bid: 16.10 Ask: 16.35 OI: 4210
AAPL271217C00285000 $185.00 Bid: 12.30 Ask: 12.55 OI: 5678
AAPL271217C00290000 $190.00 Bid: 9.05 Ask: 9.25 OI: 3120
```
### Context + Response
```go
ctx := context.Background()
chain, resp, err := client.Options.Chain(ctx, "AAPL",
options.WithExpiry(options.InDTE(45)),
options.WithStrike(options.StrikeRange(150, 200)),
options.WithSide(options.SideCall),
options.WithMinOpenInterest(100),
)
if err != nil {
log.Fatal(err)
}
if chain == nil {
fmt.Println("No data for the requested chain.")
return
}
fmt.Println(chain) // OptionsChain{Underlying: AAPL, Contracts: 3, Updated: ...}
for _, o := range chain.Options {
fmt.Println(o)
}
// Rate-limit metadata from the raw response.
fmt.Printf("Requests remaining: %d (resets %s)\n",
resp.RateLimit.Remaining, resp.RateLimit.ResetAt.Format("2006-01-02 15:04:05"))
```
### By delta
```go
ctx := context.Background()
// The ~30-delta calls near a target expiration.
// ByDelta returns both sides, so pair it with WithSide to keep only calls.
chain, _, err := client.Options.Chain(ctx, "AAPL",
options.WithExpiry(options.OnExpiration(time.Date(2026, time.January, 16, 0, 0, 0, 0, time.UTC))),
options.WithStrike(options.ByDelta(0.30)),
options.WithSide(options.SideCall),
)
if err != nil {
log.Fatal(err)
}
for _, o := range chain.Options {
fmt.Printf("$%.2f Delta: %.4f Mid: %.2f\n", o.Strike, o.Delta, o.Mid)
}
```
### Liquid contracts
```go
ctx := context.Background()
// Liquid, tight-spread contracts in a specific month, weeklies excluded.
chain, _, err := client.Options.Chain(ctx, "AAPL",
options.WithExpiry(options.InMonthOfYear(1, 2026)),
options.WithExpirationTypes(options.ExcludeExpirationTypes(options.Weekly)),
options.WithMinVolume(100),
options.WithMinOpenInterest(1000),
options.WithMaxBidAskSpread(0.10),
options.WithStrikeLimit(20),
)
if err != nil {
log.Fatal(err)
}
for _, o := range chain.Options {
fmt.Printf("%s Vol: %d OI: %d Spread: %.2f\n",
o.OptionSymbol, o.Volume, o.OpenInterest, o.Spread())
}
```
### Historical snapshot
```go
ctx := context.Background()
// The chain as it stood 30 days ago, narrowed to a strike range.
chain, _, err := client.Options.Chain(ctx, "AAPL",
options.WithChainDate(time.Now().AddDate(0, 0, -30)),
options.WithStrike(options.StrikeRange(150, 200)),
options.WithSide(options.SideCall),
)
if err != nil {
log.Fatal(err)
}
if chain != nil {
fmt.Printf("%d contracts as of the snapshot date\n", len(chain.Options))
}
```
## OptionsChain
```go
type OptionsChain struct {
Underlying string // the underlying stock symbol
Options []OptionQuote // one quote per contract in the chain
Updated time.Time // when the chain was last updated
}
```
`OptionsChain` is the result of a `Chain` request: the set of contracts listed
for a single underlying after any server-side filters have been applied.
- `Underlying` (`string`) — the underlying stock symbol.
- `Options` (`[]OptionQuote`) — the contracts in the chain, each an [`OptionQuote`](#optionquote).
- `Updated` (`time.Time`) — when the chain was last updated (Eastern time).
`OptionsChain` also provides `String() string`, which returns a summary such as
`OptionsChain{Underlying: AAPL, Contracts: 3, Updated: 2026-07-11 16:00:00}`.
## OptionQuote
```go
type OptionQuote struct {
OptionSymbol string `json:"optionSymbol"` // OCC option symbol
Underlying string `json:"underlying"` // underlying stock symbol
Expiration time.Time `json:"expiration"` // expiration date
Strike float64 `json:"strike"` // strike price
Type OptionType `json:"side"` // "call" or "put"
Bid float64 `json:"bid"` // bid price
BidSize int `json:"bidSize"` // bid size
Ask float64 `json:"ask"` // ask price
AskSize int `json:"askSize"` // ask size
Last float64 `json:"last"` // last trade price
Volume int64 `json:"volume"` // trading volume
OpenInterest int64 `json:"openInterest"` // open interest
IV float64 `json:"iv"` // implied volatility
Delta float64 `json:"delta"` // delta greek
Gamma float64 `json:"gamma"` // gamma greek
Theta float64 `json:"theta"` // theta greek
Vega float64 `json:"vega"` // vega greek
Mid float64 `json:"mid"` // midpoint price from the API
UnderlyingPrice float64 `json:"underlyingPrice"` // current price of the underlying
IntrinsicValue float64 `json:"intrinsicValue"` // intrinsic value
ExtrinsicValue float64 `json:"extrinsicValue"` // extrinsic (time) value
FirstTraded time.Time `json:"firstTraded"` // date the option was first traded
DTE int `json:"dte"` // days to expiration
InTheMoney bool `json:"inTheMoney"` // whether the option is ITM
Updated time.Time `json:"updated"` // when this contract was last updated
}
```
`OptionQuote` is a quote for a single option contract, including pricing,
volume, open interest, implied volatility, and the Greeks. It is used both for
the entries of an `OptionsChain` and for the responses of
[`Quote` and `Quotes`](https://www.marketdata.app/docs/sdk/go/options/quotes). Timestamps are normalized to
Eastern time.
### Methods
- `String() string` — a one-line summary of the contract.
- `Spread() float64` — the bid-ask spread (`Ask` minus `Bid`).
- `CalcMid() float64` — the bid-ask midpoint computed locally from `Bid` and `Ask`, unlike the `Mid` field, which is the midpoint reported by the API.
> [!NOTE]
> **[Zero values and null data]**
>
> Numeric fields use Go value types (`float64`, `int64`), not pointers. When the
> API returns null for a field it is decoded as the zero value (`0` or `0.0`). For
> `IV` and the Greeks (`Delta`, `Gamma`, `Theta`, `Vega`), a zero value may mean
> the figure was not calculable rather than a true zero.
---
# Expirations (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/options/expirations/index.md
Get the list of expiration dates that have listed option contracts for an
underlying symbol, returned in Eastern time.
## Making Requests
`Expirations` returns a `[]time.Time` of expiration dates. Two optional,
independent filters narrow the list. Choose the call style that fits your code:
| Method | Return | Description |
|-----------------------------------------|--------------------------------------------|-----------------------------------------------------------------------|
| **`GetExpirations(symbol, ...opts)`** | `([]time.Time, error)` | Convenience; uses a background context. |
| **`Expirations(ctx, symbol, ...opts)`** | `([]time.Time, *response.Response, error)` | Context-aware; also returns the raw response and rate-limit metadata. |
## Expirations
```go
func (s *Service) Expirations(ctx context.Context, symbol string, opts ...ExpirationOption) ([]time.Time, *response.Response, error)
func (s *Service) GetExpirations(symbol string, opts ...ExpirationOption) ([]time.Time, error)
```
`Expirations` fetches the dates on which the underlying has listed option
contracts. The symbol is required; without any options the endpoint returns
every expiration in the chain. The dates are normalized to Eastern time (the
exchange time zone).
### Parameters
- `symbol` (`string`) — the underlying stock symbol (for example `"AAPL"`). Required; an empty string is rejected with a `marketdata.ValidationError` before any request is made.
- Options (independent — may be combined):
- `options.WithExpirationStrike(strike float64)` — limit the list to expirations that offer a contract at the given strike price. The value must be greater than zero to take effect.
- `options.WithExpirationDate(d time.Time)` — return the expirations that were available on the given historical date instead of today's list. Only the calendar date is used; a zero time leaves the parameter unset.
### Returns
- `[]time.Time` — the expiration dates, in Eastern time. Empty when there is no data.
- `*response.Response` — the raw response plus rate-limit metadata (context method only).
- `error` — non-nil on a validation failure, transport error, or unexpected API status.
### Notes
- The two options are independent and combine freely (unlike the chain's strike and expiry selectors).
- If the API has no data for the request (HTTP 404), `Expirations` returns a `nil` slice, a response whose `NoData` field is `true`, and a `nil` error.
### Get (simple)
```go
package main
import (
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
expirations, err := client.Options.GetExpirations("AAPL")
if err != nil {
log.Fatal(err)
}
fmt.Println("AAPL option expirations:")
for _, exp := range expirations {
fmt.Println(exp.Format("2006-01-02"))
}
}
```
**Output**
```
AAPL option expirations:
2026-07-17
2026-07-24
2026-07-31
2026-08-21
2026-09-18
2026-12-18
2027-01-15
2027-06-17
...
```
### Context + Response
```go
ctx := context.Background()
expirations, resp, err := client.Options.Expirations(ctx, "AAPL")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%d expirations\n", len(expirations))
fmt.Printf("Requests remaining: %d\n", resp.RateLimit.Remaining)
```
### With options
```go
ctx := context.Background()
// Only expirations that list a $200 strike, as they stood one week ago.
expirations, _, err := client.Options.Expirations(ctx, "AAPL",
options.WithExpirationStrike(200),
options.WithExpirationDate(time.Now().AddDate(0, 0, -7)),
)
if err != nil {
log.Fatal(err)
}
for _, exp := range expirations {
fmt.Println(exp.Format("2006-01-02"))
}
```
## Return Value
`Expirations` returns a plain `[]time.Time`. Each element is an expiration
date normalized to Eastern time; call `Format("2006-01-02")` to render the
calendar date. There is no wrapper struct for this endpoint.
---
# Lookup (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/options/lookup/index.md
Resolve an option contract to its standard OCC option symbol from the
underlying symbol, expiration date, strike price, and option type.
## Making Requests
`Lookup` takes the four contract attributes as ordinary Go arguments and
returns the OCC option symbol as a string, suitable for use with
[Quote and Quotes](https://www.marketdata.app/docs/sdk/go/options/quotes). Choose the call style that fits
your code:
| Method | Return | Description |
|---------------------------------------------------------------|---------------------------------------|-----------------------------------------------------------------------|
| **`GetLookup(underlying, expiration, strike, optionType)`** | `(string, error)` | Convenience; uses a background context. |
| **`Lookup(ctx, underlying, expiration, strike, optionType)`** | `(string, *response.Response, error)` | Context-aware; also returns the raw response and rate-limit metadata. |
## Lookup
```go
func (s *Service) Lookup(ctx context.Context, underlying string, expiration time.Time, strike float64, optionType OptionType) (string, *response.Response, error)
func (s *Service) GetLookup(underlying string, expiration time.Time, strike float64, optionType OptionType) (string, error)
```
`Lookup` builds an OCC option symbol (for example `AAPL271217C00250000`) from
a contract description. The underlying symbol, the expiration date, the strike
price, and the option type are all required. The resolved symbol can then be
passed to [`Quote`](https://www.marketdata.app/docs/sdk/go/options/quotes) or
[`Quotes`](https://www.marketdata.app/docs/sdk/go/options/quotes).
### Parameters
- `underlying` (`string`) — the underlying stock symbol (for example `"AAPL"`). Required; an empty string is rejected with a `marketdata.ValidationError` before any request is made.
- `expiration` (`time.Time`) — the contract's expiration date. Only the calendar date is used (formatted as `2006-01-02`).
- `strike` (`float64`) — the strike price (for example `150.0`).
- `optionType` (`options.OptionType`) — the contract type: `options.Call` or `options.Put`.
### Returns
- `string` — the OCC option symbol. Empty when the contract cannot be resolved (see Notes).
- `*response.Response` — the raw response plus rate-limit metadata (context method only).
- `error` — non-nil on a validation failure, transport error, or unexpected API status.
### Notes
- The OCC symbol format is `SYMBOL + YYMMDD + C/P + STRIKE (8 digits)`. For example, `AAPL271217C00250000` is an AAPL January 17, 2025 $150 call.
- If the API cannot resolve the contract (HTTP 404), `Lookup` returns an empty string, a response whose `NoData` field is `true`, and a `nil` error. Check for an empty string before using the result.
- The lookup query is sent as a single URL path segment; there are no query parameters and no functional options for this endpoint.
### Get (simple)
```go
package main
import (
"fmt"
"log"
"time"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
"github.com/MarketDataApp/sdk-go/v2/marketdata/options"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
expiration := time.Date(2025, time.January, 17, 0, 0, 0, 0, time.UTC)
symbol, err := client.Options.GetLookup("AAPL", expiration, 150.0, options.Call)
if err != nil {
log.Fatal(err)
}
if symbol == "" {
fmt.Println("Contract could not be resolved.")
return
}
fmt.Printf("OCC symbol: %s\n", symbol)
}
```
**Output**
```
OCC symbol: AAPL271217C00250000
```
### Context + Response
```go
ctx := context.Background()
expiration := time.Date(2025, time.January, 17, 0, 0, 0, 0, time.UTC)
symbol, resp, err := client.Options.Lookup(ctx, "AAPL", expiration, 150.0, options.Call)
if err != nil {
log.Fatal(err)
}
fmt.Printf("OCC symbol: %s\n", symbol)
fmt.Printf("Requests remaining: %d\n", resp.RateLimit.Remaining)
```
### Lookup then Quote
```go
ctx := context.Background()
expiration := time.Date(2025, time.January, 17, 0, 0, 0, 0, time.UTC)
// Resolve the OCC symbol, then fetch its quote.
symbol, _, err := client.Options.Lookup(ctx, "AAPL", expiration, 150.0, options.Call)
if err != nil {
log.Fatal(err)
}
quote, _, err := client.Options.Quote(ctx, symbol)
if err != nil {
log.Fatal(err)
}
if quote != nil {
fmt.Printf("%s Bid: %.2f Ask: %.2f\n", quote.OptionSymbol, quote.Bid, quote.Ask)
}
```
## OptionType
```go
type OptionType string
const (
Call OptionType = "call"
Put OptionType = "put"
)
```
`OptionType` identifies a contract as a call or a put. It is the required
contract-type argument to `Lookup` and the `Type` field of an
[`OptionQuote`](https://www.marketdata.app/docs/sdk/go/options/quotes). Use the `options.Call` and
`options.Put` constants.
---
# Option Quotes (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/options/quotes/index.md
Fetch current or historical end-of-day quotes for one option contract with
`Quote`, or for many contracts at once with `Quotes`.
## Making Requests
Both methods work with OCC option symbols (resolve one with
[`Lookup`](https://www.marketdata.app/docs/sdk/go/options/lookup) or read them off a
[`Chain`](https://www.marketdata.app/docs/sdk/go/options/chain)). Each has a context-aware form and a
convenience `Get*` wrapper:
| Method | Return | Description |
|--------------------------------------------|----------------------------------------------|----------------------------------------------------------------------------|
| **`GetQuote(optionSymbol, ...opts)`** | `(*OptionQuote, error)` | Single contract; convenience, background context. |
| **`Quote(ctx, optionSymbol, ...opts)`** | `(*OptionQuote, *response.Response, error)` | Single contract; context-aware, plus raw response and rate-limit metadata. |
| **`GetQuotes(optionSymbols ...string)`** | `([]OptionQuote, error)` | Multiple contracts; convenience, background context. |
| **`Quotes(ctx, optionSymbols ...string)`** | `([]OptionQuote, *response.Response, error)` | Multiple contracts; context-aware, fetched concurrently. |
## Quote
```go
func (s *Service) Quote(ctx context.Context, optionSymbol string, opts ...OptionQuoteOption) (*OptionQuote, *response.Response, error)
func (s *Service) GetQuote(optionSymbol string, opts ...OptionQuoteOption) (*OptionQuote, error)
```
`Quote` fetches a real-time or historical quote for a single option contract,
identified by its OCC option symbol (for example `AAPL271217C00250000`). The
option symbol is required.
### Parameters
- `optionSymbol` (`string`) — the OCC option symbol. Required; an empty string is rejected with a `marketdata.ValidationError` before any request is made.
- Options:
- `options.WithOptionQuoteWindow(w OptionQuoteWindow)` — request a historical quote from a single [`OptionQuoteWindow`](#optionquotewindow) value. Omitting it returns the current quote. Build the window with exactly one of:
- `options.QuoteOnDate(t time.Time)` — the contract's quote on a single historical date (`date=YYYY-MM-DD`).
- `options.QuoteRange(from, to time.Time)` — the contract's quotes across an explicit date range (`from=...&to=...`).
### Returns
- `*OptionQuote` — the contract quote. `nil` when there is no data (see Notes).
- `*response.Response` — the raw response plus rate-limit metadata (context method only).
- `error` — non-nil on a validation failure, transport error, or unexpected API status.
### Notes
- **The date window is compile-time exclusive.** Because `OptionQuoteWindow` is a single sealed-union value, the API's mutually-exclusive `date` and `from`/`to` parameters can never be combined by mistake (sending both returns HTTP 400).
- **Validation happens before the network call.** A zero date, or a `from` after its `to`, is rejected with a `marketdata.ValidationError` before any request is made.
- **No-data behavior.** If the API has no data for the request (HTTP 404), `Quote` returns a `nil` quote, a response whose `NoData` field is `true`, and a `nil` error. Check for a `nil` quote before use.
## Quotes
```go
func (s *Service) Quotes(ctx context.Context, optionSymbols ...string) ([]OptionQuote, *response.Response, error)
func (s *Service) GetQuotes(optionSymbols ...string) ([]OptionQuote, error)
```
`Quotes` fetches quotes for multiple contracts. It fans out one concurrent
`Quote` request per symbol, drawing slots from the client's shared concurrency
pool (at most 50 in-flight requests per client across all services), and merges
the results into a single slice in the order the symbols were given.
### Parameters
- `optionSymbols` (`...string`) — one or more OCC option symbols (variadic). At least one is required; passing none is rejected with a `marketdata.ValidationError`.
### Returns
- `[]OptionQuote` — one quote per contract that returned data, in input order.
- `*response.Response` — the raw response for one of the individual requests, not an aggregate (context method only).
- `error` — non-nil if any request fails with a real error; in that case no quotes are returned.
### Notes
- **Symbols with no data are omitted.** A contract that returns HTTP 404 is dropped from the result rather than producing an error, so the returned slice may be shorter than the input.
- **`Quotes` does not accept a quote window.** The multi-symbol method fetches current quotes only; use `Quote` with `WithOptionQuoteWindow` for historical data on a single contract.
### Get (single)
```go
package main
import (
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
quote, err := client.Options.GetQuote("AAPL271217C00250000")
if err != nil {
log.Fatal(err)
}
if quote == nil {
fmt.Println("No data for the requested contract.")
return
}
fmt.Printf("Symbol: %s\n", quote.OptionSymbol)
fmt.Printf("Underlying: %s @ %.2f\n", quote.Underlying, quote.UnderlyingPrice)
fmt.Printf("Strike: $%.2f %s\n", quote.Strike, quote.Type)
fmt.Printf("Bid/Ask: %.2f x %d / %.2f x %d\n", quote.Bid, quote.BidSize, quote.Ask, quote.AskSize)
fmt.Printf("Last: %.2f\n", quote.Last)
fmt.Printf("Volume / OI: %d / %d\n", quote.Volume, quote.OpenInterest)
fmt.Printf("IV / Delta: %.4f / %.4f\n", quote.IV, quote.Delta)
}
```
**Output**
```
Symbol: AAPL271217C00250000
Underlying: AAPL @ 195.20
Strike: $150.00 call
Bid/Ask: 45.30 x 45 / 45.65 x 30
Last: 45.50
Volume / OI: 1234 / 15678
IV / Delta: 0.2841 / 0.9210
```
### Context + Response
```go
ctx := context.Background()
quote, resp, err := client.Options.Quote(ctx, "AAPL271217C00250000")
if err != nil {
log.Fatal(err)
}
if quote == nil {
fmt.Println("No data for the requested contract.")
return
}
fmt.Println(quote) // OptionQuote.String() summary
fmt.Printf("Requests remaining: %d\n", resp.RateLimit.Remaining)
```
### Historical (single)
```go
ctx := context.Background()
// Quote on one historical date.
onDate, _, err := client.Options.Quote(ctx, "AAPL271217C00250000",
options.WithOptionQuoteWindow(options.QuoteOnDate(
time.Date(2025, time.January, 2, 0, 0, 0, 0, time.UTC))))
if err != nil {
log.Fatal(err)
}
if onDate != nil {
fmt.Printf("Last: %.2f IV: %.4f\n", onDate.Last, onDate.IV)
}
// A series of end-of-day quotes across a range.
series, _, err := client.Options.Quote(ctx, "AAPL271217C00250000",
options.WithOptionQuoteWindow(options.QuoteRange(
time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC),
time.Date(2025, time.January, 31, 0, 0, 0, 0, time.UTC))))
if err != nil {
log.Fatal(err)
}
_ = series
```
### Multiple symbols
```go
ctx := context.Background()
// Variadic list of OCC symbols, fetched concurrently.
quotes, _, err := client.Options.Quotes(ctx,
"AAPL271217C00280000",
"AAPL271217C00290000",
"AAPL271217C00300000",
"AAPL271217P00280000",
)
if err != nil {
log.Fatal(err)
}
for _, q := range quotes {
fmt.Printf("%s $%.0f %s Bid: %.2f Ask: %.2f Vol: %d\n",
q.OptionSymbol, q.Strike, q.Type, q.Bid, q.Ask, q.Volume)
}
```
## OptionQuoteWindow
```go
type OptionQuoteWindow interface {
// contains filtered or unexported methods
}
func QuoteOnDate(t time.Time) OptionQuoteWindow
func QuoteRange(from, to time.Time) OptionQuoteWindow
```
`OptionQuoteWindow` is the sealed-union date selector for a single-contract
`Quote` request, passed via `options.WithOptionQuoteWindow`. Build it with
exactly one of `options.QuoteOnDate` (a single day) or `options.QuoteRange` (an
explicit range). Only the calendar date of each `time.Time` is used. Because it
is one value, the API's mutually-exclusive `date` and `from`/`to` parameters
cannot be combined.
## OptionQuote
```go
type OptionQuote struct {
OptionSymbol string `json:"optionSymbol"` // OCC option symbol
Underlying string `json:"underlying"` // underlying stock symbol
Expiration time.Time `json:"expiration"` // expiration date
Strike float64 `json:"strike"` // strike price
Type OptionType `json:"side"` // "call" or "put"
Bid float64 `json:"bid"` // bid price
BidSize int `json:"bidSize"` // bid size
Ask float64 `json:"ask"` // ask price
AskSize int `json:"askSize"` // ask size
Last float64 `json:"last"` // last trade price
Volume int64 `json:"volume"` // trading volume
OpenInterest int64 `json:"openInterest"` // open interest
IV float64 `json:"iv"` // implied volatility
Delta float64 `json:"delta"` // delta greek
Gamma float64 `json:"gamma"` // gamma greek
Theta float64 `json:"theta"` // theta greek
Vega float64 `json:"vega"` // vega greek
Mid float64 `json:"mid"` // midpoint price from the API
UnderlyingPrice float64 `json:"underlyingPrice"` // current price of the underlying
IntrinsicValue float64 `json:"intrinsicValue"` // intrinsic value
ExtrinsicValue float64 `json:"extrinsicValue"` // extrinsic (time) value
FirstTraded time.Time `json:"firstTraded"` // date the option was first traded
DTE int `json:"dte"` // days to expiration
InTheMoney bool `json:"inTheMoney"` // whether the option is ITM
Updated time.Time `json:"updated"` // when this contract was last updated
}
```
`OptionQuote` is the quote for a single option contract, returned by both
`Quote` (as `*OptionQuote`) and `Quotes` (as elements of `[]OptionQuote`), and
also used for the entries of an [`OptionsChain`](https://www.marketdata.app/docs/sdk/go/options/chain).
Timestamps are normalized to Eastern time.
### Methods
- `String() string` — a one-line summary of the contract.
- `Spread() float64` — the bid-ask spread (`Ask` minus `Bid`).
- `CalcMid() float64` — the bid-ask midpoint computed locally from `Bid` and `Ask`, unlike the `Mid` field, which is the midpoint reported by the API.
> [!NOTE]
> **[Zero values and null data]**
>
> Numeric fields use Go value types (`float64`, `int64`), not pointers. When the
> API returns null for a field it is decoded as the zero value (`0` or `0.0`). For
> `IV` and the Greeks (`Delta`, `Gamma`, `Theta`, `Vega`), a zero value may mean
> the figure was not calculable rather than a true zero.
---
# Parameters (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/parameters/index.md
The Market Data API supports a set of **universal parameters** that work on every endpoint. In the Go SDK these are configured as **client-level functional options** passed to `NewClient`; once set, they are attached to every request that client makes.
```go
client, err := marketdata.NewClient(
marketdata.WithMode(marketdata.ModeCached),
marketdata.WithMaxAge("5min"),
marketdata.WithLimit(50),
marketdata.WithColumns("open", "high", "low", "close", "volume"),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
```
## How Parameters Are Resolved
Universal parameters are merged from three sources, lowest priority first:
1. **Environment variables** (lowest) — read once at `NewClient` time. Only four universal parameters have environment-variable support (see the table below).
2. **Client options** (`With*`) — override any value that came from the environment.
3. **Endpoint method-level parameters** (highest) — some endpoints set a specific value themselves (for example, candle endpoints pin the `dateformat` they need for correct decoding). A method-level value always wins over the client default.
> [!NOTE]
> **[Client-level, not per-call]**
>
> Unlike the PHP SDK, the Go SDK does not accept a universal-parameter object on each method call. Universal parameters are set on the client. If you need different values for different calls — for example a cached client for bulk quotes and a live client for time-sensitive calls — create separate clients.
## Environment Variables
Only these four universal parameters can be set through the environment. They are read when the client is created and can be overridden by the corresponding `With*` option.
| Variable | Values | Maps to | Option |
|---------------------------------|------------------------------------|--------------|---------------------|
| `MARKETDATA_DATE_FORMAT` | `timestamp`, `unix`, `spreadsheet` | `dateformat` | `WithDateFormat` |
| `MARKETDATA_COLUMNS` | comma-separated list | `columns` | `WithColumns` |
| `MARKETDATA_ADD_HEADERS` | `true`, `false` | `headers` | `WithAddHeaders` |
| `MARKETDATA_USE_HUMAN_READABLE` | `true`, `false` | `human` | `WithHumanReadable` |
> [!NOTE]
> `mode`, `maxage`, `limit`, and `offset` have **no** environment-variable support in the Go SDK; set them with their `With*` options.
## Available Parameters
### Mode
Controls how each request is fulfilled, trading data freshness against credit cost.
- **Option:** `WithMode(mode Mode)`
- **Values:** `marketdata.ModeLive` (real-time; default on paid plans), `marketdata.ModeCached` (recently cached data at reduced credit cost), `marketdata.ModeDelayed` (delayed at least 15 minutes; paid plans only)
```go
client, err := marketdata.NewClient(marketdata.WithMode(marketdata.ModeLive))
```
> [!NOTE]
> **[Premium parameter]**
>
> `mode` is available only on paid plans. Free and trial plans receive historical data (the last fully-closed session) regardless of this setting. See [Data Freshness](https://www.marketdata.app/docs/account/data-freshness).
When `ModeCached` misses the cache, the API returns HTTP `204`, which the SDK surfaces as a no-data response (nil result, `Response.NoData == true`, nil error) rather than an error.
### Max Age
The maximum age of cached data accepted when `ModeCached` is in effect. It has no effect in other modes.
- **Option:** `WithMaxAge(maxAge string)`
- **Values:** a relative duration such as `"5min"` or `"1h"`, or an absolute datetime. If no cached data falls within the window, the API returns a no-data response at no credit cost.
```go
client, err := marketdata.NewClient(
marketdata.WithMode(marketdata.ModeCached),
marketdata.WithMaxAge("5min"),
)
```
### Columns
Restricts responses to the named columns, which can reduce payload size. Missing columns simply decode as zero values, so filtering never causes a decode error. Passing no columns is a no-op.
- **Option:** `WithColumns(columns ...string)`
```go
client, err := marketdata.NewClient(
marketdata.WithColumns("open", "close", "volume"),
)
```
### Limit and Offset
Cap and paginate results.
- **Options:** `WithLimit(n int)`, `WithOffset(n int)`
- Values ≤ 0 are ignored (offset 0 is the default first page).
```go
client, err := marketdata.NewClient(
marketdata.WithLimit(100),
marketdata.WithOffset(100), // second page of 100
)
```
### Date Format
Sets the wire representation of date/time fields.
- **Option:** `WithDateFormat(format string)`
- **Values:** `"timestamp"`, `"unix"`, `"spreadsheet"`
> [!WARNING]
> **[Advanced use only]**
>
> The SDK decodes dates from the API's default numeric (unix) representation. Overriding `dateformat` globally can change the wire format of date fields and cause typed responses (candles, quotes, earnings) to fail decoding. Endpoints that require a specific format set it themselves at the method level. Only set this when you consume the raw response yourself.
### Human Readable
Requests human-readable field names and value formatting via the `human` parameter.
- **Option:** `WithHumanReadable(enabled bool)`
> [!WARNING]
> **[Advanced use only]**
>
> Human-readable output changes field names and value formatting and is incompatible with the SDK's typed decoding. Enable it only when you read the raw `*marketdata.Response` body yourself instead of using the typed result.
### Add Headers
Controls whether a header row is included in CSV output via the `headers` parameter.
- **Option:** `WithAddHeaders(enabled bool)`
This affects CSV output only and has no effect on the JSON responses the SDK decodes; it is provided for completeness.
## Reading the Raw Response
The advanced parameters above are most useful when you access the raw response body rather than the typed result. Use the context-form call and the `*marketdata.Response` it returns (see [Client](https://www.marketdata.app/docs/sdk/go/client)):
```go
_, resp, err := client.Stocks.Candles(ctx, "AAPL")
if err != nil {
log.Fatal(err)
}
fmt.Println("content-type:", resp.Header.Get("Content-Type"))
```
## Next Steps
- Review the [Client](https://www.marketdata.app/docs/sdk/go/client) options that set these parameters.
- Learn how the SDK reports failures on the [Errors](https://www.marketdata.app/docs/sdk/go/errors) page.
---
# Stocks (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/stocks/index.md
Access real-time and historical stock market data — quotes, OHLCV candles, bulk candles, SmartMid prices, earnings reports, and news — through the `client.Stocks` service.
---
# Bulk Candles (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/stocks/bulkcandles/index.md
Retrieve a single daily candle for many stock symbols in one API request — ideal for market snapshots.
## Making Requests
`BulkCandles` lives on the `Stocks` service (`client.Stocks`). Unlike the other stocks endpoints it has no `Get*` convenience wrapper; call the context method directly.
| Method | Return | Description |
|------------------------------------------|---------------------------------------------|---------------------------------------------------------------------|
| **`BulkCandles(ctx, symbols, opts...)`** | `([]BulkCandle, *response.Response, error)` | Context-aware; also returns the raw response + rate-limit metadata. |
## BulkCandles
```go
func (s *Service) BulkCandles(ctx context.Context, symbols []string, opts ...BulkCandleOption) ([]BulkCandle, *response.Response, error)
```
Fetch one daily candle per symbol. Unlike [Candles](https://www.marketdata.app/docs/sdk/go/stocks/candles), this endpoint returns a single candle for each symbol, not a time series.
### Parameters
- `ctx` (`context.Context`) — controls cancellation and deadlines.
- `symbols` (`[]string`) — the stock ticker symbols, e.g. `[]string{"AAPL", "MSFT", "GOOG"}`. At least one symbol is required; an empty slice returns a validation error without making a request.
- Options (`stocks.BulkCandleOption`):
- `stocks.WithBulkResolution(r Resolution)` — set the resolution. The endpoint only supports `stocks.ResolutionDaily`, which is also the default.
- `stocks.WithBulkDate(t time.Time)` — request candles for a specific historical date instead of the most recent trading day. Only the calendar date is used.
- `stocks.WithSnapshot(snapshot bool)` — control whether the API returns a snapshot of the latest candle. When omitted, the parameter is not sent and the API default applies.
- `stocks.WithAdjustSplits(adjust bool)` — adjust for stock splits. When omitted, the parameter is not sent and the API default applies.
- `stocks.WithAdjustDividends(adjust bool)` — adjust for dividends. The API adjusts for dividends by default; pass `false` for raw, unadjusted prices. When omitted, the parameter is not sent.
### Returns
- `[]BulkCandle` — one candle per symbol.
- `*response.Response` — raw response + rate-limit metadata.
- `error` — non-nil on request/validation failure.
### Notes
- Only `ResolutionDaily` is supported; `WithBulkResolution` exists for symmetry with the other candle endpoints.
- The bulk candles endpoint accepts a single date only (no range or countback mode), so `WithBulkDate` is a plain option rather than a `DateWindow`.
- If the API responds with 404 because no data is available, `BulkCandles` returns a `nil` slice, a `nil` error, and a non-nil `*response.Response` whose `NoData` field is `true`.
### Multiple symbols
```go
package main
import (
"context"
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
candles, resp, err := client.Stocks.BulkCandles(ctx, []string{"AAPL", "MSFT", "GOOG"})
if err != nil {
log.Fatal(err)
}
if resp.NoData {
fmt.Println("no candles available")
return
}
for _, c := range candles {
fmt.Printf("%s: O:%.2f C:%.2f V:%d\n", c.Symbol, c.Open, c.Close, c.Volume)
}
}
```
**Output**
```
AAPL: O:185.64 C:186.19 V:48234500
MSFT: O:378.25 C:380.55 V:22156800
GOOG: O:142.35 C:143.12 V:18456200
```
### With options
```go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
"github.com/MarketDataApp/sdk-go/v2/marketdata/stocks"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
date := time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC)
candles, _, err := client.Stocks.BulkCandles(ctx, []string{"AAPL", "MSFT", "GOOG"},
stocks.WithBulkResolution(stocks.ResolutionDaily),
stocks.WithBulkDate(date),
stocks.WithAdjustSplits(true),
)
if err != nil {
log.Fatal(err)
}
for _, c := range candles {
fmt.Println(c) // BulkCandle implements Stringer
}
}
```
## BulkCandle
```go
type BulkCandle struct {
Symbol string `json:"symbol"` // Stock ticker symbol
Time time.Time `json:"t"` // Candle timestamp (US Eastern)
Open float64 `json:"o"` // Opening price
High float64 `json:"h"` // Highest price
Low float64 `json:"l"` // Lowest price
Close float64 `json:"c"` // Closing price
Volume int64 `json:"v"` // Trading volume
}
```
Represents a single daily candle for one symbol. It carries the same OHLCV fields as [`Candle`](https://www.marketdata.app/docs/sdk/go/stocks/candles) plus the `Symbol` the candle belongs to, since bulk requests cover multiple symbols. Timestamps are normalized to US Eastern time.
### Fields
- `Symbol` — the stock ticker symbol.
- `Time` — the candle timestamp.
- `Open` / `High` / `Low` / `Close` — the OHLC prices for the day.
- `Volume` — trading volume for the day.
### Helper Methods
- `String() string` — a concise one-line summary of the candle.
---
# Stock Candles (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/stocks/candles/index.md
Retrieve historical OHLCV (open, high, low, close, volume) candles for a stock symbol across intraday, daily, weekly, monthly, and yearly resolutions.
## Making Requests
Both call styles live on the `Stocks` service (`client.Stocks`). Use `GetCandles` for the simplest call, or `Candles` when you also need the raw response and rate-limit metadata.
| Method | Return | Description |
|-------------------------------------|-----------------------------------------|--------------------------------------------------------------------------------------|
| **`GetCandles(symbol, opts...)`** | `([]Candle, error)` | Convenience wrapper; uses `context.Background()` and discards the response metadata. |
| **`Candles(ctx, symbol, opts...)`** | `([]Candle, *response.Response, error)` | Context-aware; also returns the raw response + rate-limit metadata. |
## Candles
```go
func (s *Service) Candles(ctx context.Context, symbol string, opts ...CandleOption) ([]Candle, *response.Response, error)
func (s *Service) GetCandles(symbol string, opts ...CandleOption) ([]Candle, error)
```
Fetch historical candles for one symbol. The resolution defaults to daily; the date range is expressed as a single [`DateWindow`](#date-windows) value.
### Parameters
- `ctx` (`context.Context`) — controls cancellation and deadlines (context method only).
- `symbol` (`string`) — the stock ticker symbol, e.g. `"AAPL"`. Required; an empty string returns a validation error without making a request.
- Options (`stocks.CandleOption`):
- `stocks.WithResolution(r Resolution)` — set the candle timeframe. Defaults to `stocks.ResolutionDaily`. See [Resolution values](#resolution) below.
- `stocks.WithCandleWindow(w DateWindow)` — set the date range as a single `DateWindow` value (for example `stocks.Between(from, to)` or `stocks.LastN(30)`). When omitted, the API returns its default recent window. See [Date windows](#date-windows).
- `stocks.WithCandleExtended(extended bool)` — include extended-hours data. When omitted, the parameter is not sent and the API default applies. Only meaningful for intraday resolutions.
- `stocks.WithCandleAdjustSplits(adjust bool)` — adjust for stock splits. When omitted, the parameter is not sent and the API default applies.
- `stocks.WithCandleAdjustDividends(adjust bool)` — adjust for dividends. The API adjusts for dividends by default; pass `false` for raw, unadjusted prices. When omitted, the parameter is not sent.
### Returns
- `[]Candle` — candles in chronological order.
- `*response.Response` — raw response + rate-limit metadata (context method only).
- `error` — non-nil on request/validation failure.
### Notes
- Only the calendar date of each `time.Time` in a window is used; the time-of-day and zone are ignored.
- For intraday resolutions (`Resolution1Min` through `Resolution4Hour`), when both `from` and `to` are set and the range spans more than one year, `Candles` automatically splits the range into year-sized chunks, fetches them concurrently, and merges the results in chronological order. In that case the returned `*response.Response` reflects only one of the underlying requests.
- The date range is a single `DateWindow` value, so the API's mutually-exclusive date parameters (`date`, `from`/`to`, `countback`) can never be combined by mistake.
- If the API responds with 404 because no data is available, `Candles` returns a `nil` slice, a `nil` error, and a non-nil `*response.Response` whose `NoData` field is `true`.
### Get (simple)
```go
package main
import (
"fmt"
"log"
"time"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
"github.com/MarketDataApp/sdk-go/v2/marketdata/stocks"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
from := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
to := time.Date(2024, 1, 31, 0, 0, 0, 0, time.UTC)
candles, err := client.Stocks.GetCandles("AAPL",
stocks.WithResolution(stocks.ResolutionDaily),
stocks.WithCandleWindow(stocks.Between(from, to)),
)
if err != nil {
log.Fatal(err)
}
for _, c := range candles {
fmt.Printf("%s O:%.2f H:%.2f L:%.2f C:%.2f V:%d\n",
c.Time.Format("2006-01-02"), c.Open, c.High, c.Low, c.Close, c.Volume)
}
}
```
**Output**
```
2024-01-02 O:187.15 H:188.44 L:183.89 C:185.64 V:82488700
2024-01-03 O:184.22 H:185.88 L:183.43 C:184.25 V:58414500
...
```
### Context + Response
```go
package main
import (
"context"
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
"github.com/MarketDataApp/sdk-go/v2/marketdata/stocks"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
// The 30 most recent daily candles (countback).
candles, resp, err := client.Stocks.Candles(ctx, "AAPL",
stocks.WithCandleWindow(stocks.LastN(30)),
)
if err != nil {
log.Fatal(err)
}
if resp.NoData {
fmt.Println("no candles available")
return
}
fmt.Printf("Fetched %d candles\n", len(candles))
fmt.Printf("Requests remaining: %d\n", resp.RateLimit.Remaining)
}
```
### With options
```go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
"github.com/MarketDataApp/sdk-go/v2/marketdata/stocks"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
day := time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC)
// 5-minute candles for a single day, extended hours, unadjusted for splits.
candles, _, err := client.Stocks.Candles(ctx, "AAPL",
stocks.WithResolution(stocks.Resolution5Min),
stocks.WithCandleWindow(stocks.OnDate(day)),
stocks.WithCandleExtended(true),
stocks.WithCandleAdjustSplits(false),
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d intraday candles\n", len(candles))
for _, c := range candles[:3] {
fmt.Println(c) // Candle implements Stringer
}
}
```
## Resolution
`Resolution` is a string type selecting the candle timeframe. Pass a constant to `stocks.WithResolution`.
```go
type Resolution string
const (
Resolution1Min Resolution = "1" // 1 minute
Resolution3Min Resolution = "3" // 3 minutes
Resolution5Min Resolution = "5" // 5 minutes
Resolution15Min Resolution = "15" // 15 minutes
Resolution30Min Resolution = "30" // 30 minutes
Resolution45Min Resolution = "45" // 45 minutes
Resolution1Hour Resolution = "60" // 1 hour
Resolution2Hour Resolution = "120" // 2 hours
Resolution4Hour Resolution = "240" // 4 hours
ResolutionDaily Resolution = "D" // daily (default)
ResolutionWeekly Resolution = "W" // weekly
ResolutionMonthly Resolution = "M" // monthly
ResolutionYearly Resolution = "Y" // yearly
)
```
Resolutions `Resolution1Min` through `Resolution4Hour` are intraday; these are the resolutions eligible for automatic year-chunk splitting on large ranges.
## Date Windows
`DateWindow` is a sealed union describing the date range for `Candles` (and shared by [Earnings](https://www.marketdata.app/docs/sdk/go/stocks/earnings) and [News](https://www.marketdata.app/docs/sdk/go/stocks/news)). Build one with exactly one of the constructors below and pass it to `stocks.WithCandleWindow`. Because a window is a single value, illegal combinations such as "from plus countback" are not expressible.
| Constructor | Selects | API parameters |
|----------------------------|--------------------------------------|---------------------|
| `stocks.OnDate(d)` | a single calendar day | `date=d` |
| `stocks.Between(from, to)` | an explicit closed range (inclusive) | `from=from&to=to` |
| `stocks.Since(from)` | everything from a day onward | `from=from` |
| `stocks.Until(to)` | up to and including a day | `to=to` |
| `stocks.LastN(n)` | the `n` most recent periods | `countback=n` |
| `stocks.LastNUntil(n, to)` | the `n` periods ending at a day | `countback=n&to=to` |
Only the calendar date of each `time.Time` is used; the time-of-day and zone are ignored.
## Candle
```go
type Candle struct {
Time time.Time `json:"t"` // Candle timestamp (start of period, US Eastern)
Open float64 `json:"o"` // Opening price
High float64 `json:"h"` // Highest price
Low float64 `json:"l"` // Lowest price
Close float64 `json:"c"` // Closing price
Volume int64 `json:"v"` // Trading volume
}
```
Represents a single OHLCV candle. `Time` marks the start of the candle period and is normalized to US Eastern time.
### Fields
- `Time` — the candle timestamp (start of the period).
- `Open` / `High` / `Low` / `Close` — the OHLC prices for the period.
- `Volume` — trading volume for the period.
### Helper Methods
- `Range() float64` — the high-low range (`High - Low`).
- `RangePercent() float64` — the range as a percentage of `Open`.
- `IsBullish() bool` — true when `Close > Open`.
- `IsBearish() bool` — true when `Close < Open`.
- `String() string` — a concise one-line summary of the candle.
---
# Earnings (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/stocks/earnings/index.md
Get historical earnings-per-share data and the upcoming earnings calendar for a stock, one report per fiscal quarter.
> [!NOTE]
> **[Premium Endpoint]**
>
> The earnings endpoint requires a premium subscription. Free and trial accounts cannot access this data.
## Making Requests
Both call styles live on the `Stocks` service (`client.Stocks`). Use `GetEarnings` for the simplest call, or `Earnings` when you also need the raw response and rate-limit metadata.
| Method | Return | Description |
|--------------------------------------|------------------------------------------|--------------------------------------------------------------------------------------|
| **`GetEarnings(symbol, opts...)`** | `([]Earning, error)` | Convenience wrapper; uses `context.Background()` and discards the response metadata. |
| **`Earnings(ctx, symbol, opts...)`** | `([]Earning, *response.Response, error)` | Context-aware; also returns the raw response + rate-limit metadata. |
## Earnings
```go
func (s *Service) Earnings(ctx context.Context, symbol string, opts ...EarningsOption) ([]Earning, *response.Response, error)
func (s *Service) GetEarnings(symbol string, opts ...EarningsOption) ([]Earning, error)
```
Fetch earnings reports for one symbol. With no options the API returns its default set of recent and upcoming reports.
### Parameters
- `ctx` (`context.Context`) — controls cancellation and deadlines (context method only).
- `symbol` (`string`) — the stock ticker symbol, e.g. `"AAPL"`. Required; an empty string returns a validation error without making a request.
- Options (`stocks.EarningsOption`):
- `stocks.WithEarningsWindow(w DateWindow)` — set the reporting date range as a single `DateWindow` value. Build the window with any of the shared [date-window constructors](#date-windows), for example `stocks.OnDate(d)`, `stocks.Between(from, to)`, or `stocks.LastN(4)`.
### Returns
- `[]Earning` — earnings reports, one per fiscal quarter.
- `*response.Response` — raw response + rate-limit metadata (context method only).
- `error` — non-nil on request/validation failure.
### Notes
- EPS fields on the returned `Earning` values are pointers (`*float64`) and are `nil` when the API reports no value, such as for earnings not yet reported. This distinguishes a missing value from a true `$0.00`.
- Only the calendar date of each `time.Time` in a window is used; the time-of-day and zone are ignored.
- If the API responds with 404 because no data is available, `Earnings` returns a `nil` slice, a `nil` error, and a non-nil `*response.Response` whose `NoData` field is `true`.
### Get (simple)
```go
package main
import (
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
earnings, err := client.Stocks.GetEarnings("AAPL")
if err != nil {
log.Fatal(err)
}
for _, e := range earnings {
reported := "n/a"
if e.ReportedEPS != nil {
reported = fmt.Sprintf("$%.2f", *e.ReportedEPS)
}
fmt.Printf("FY%d Q%d report %s EPS %s\n",
e.FiscalYear, e.FiscalQuarter, e.ReportDate.Format("2006-01-02"), reported)
}
}
```
**Output**
```
FY2024 Q1 report 2024-01-25 EPS $2.18
FY2024 Q2 report 2024-04-25 EPS n/a
```
### Context + Response
```go
package main
import (
"context"
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
earnings, resp, err := client.Stocks.Earnings(ctx, "AAPL")
if err != nil {
log.Fatal(err)
}
if resp.NoData {
fmt.Println("no earnings available")
return
}
for _, e := range earnings {
fmt.Println(e) // Earning implements Stringer
}
fmt.Printf("Requests remaining: %d\n", resp.RateLimit.Remaining)
}
```
### With options
```go
package main
import (
"context"
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
"github.com/MarketDataApp/sdk-go/v2/marketdata/stocks"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
// The 4 most recent quarterly reports.
earnings, _, err := client.Stocks.Earnings(ctx, "AAPL",
stocks.WithEarningsWindow(stocks.LastN(4)),
)
if err != nil {
log.Fatal(err)
}
for _, e := range earnings {
est, act := "n/a", "n/a"
if e.EstimatedEPS != nil {
est = fmt.Sprintf("$%.2f", *e.EstimatedEPS)
}
if e.ReportedEPS != nil {
act = fmt.Sprintf("$%.2f", *e.ReportedEPS)
}
fmt.Printf("Q%d %d: estimate %s, actual %s\n", e.FiscalQuarter, e.FiscalYear, est, act)
}
}
```
## Date Windows
`stocks.WithEarningsWindow` takes a `DateWindow` — the same sealed-union value used by [Candles](https://www.marketdata.app/docs/sdk/go/stocks/candles) and [News](https://www.marketdata.app/docs/sdk/go/stocks/news). Build one with exactly one constructor:
| Constructor | Selects | API parameters |
|----------------------------|--------------------------------------|---------------------|
| `stocks.OnDate(d)` | a single calendar day | `date=d` |
| `stocks.Between(from, to)` | an explicit closed range (inclusive) | `from=from&to=to` |
| `stocks.Since(from)` | everything from a day onward | `from=from` |
| `stocks.Until(to)` | up to and including a day | `to=to` |
| `stocks.LastN(n)` | the `n` most recent reports | `countback=n` |
| `stocks.LastNUntil(n, to)` | the `n` reports ending at a day | `countback=n&to=to` |
## Earning
```go
type Earning struct {
Symbol string `json:"symbol"` // Stock ticker symbol
FiscalYear int `json:"fiscalYear"` // Fiscal year of the report
FiscalQuarter int `json:"fiscalQuarter"` // Fiscal quarter (1-4)
Date time.Time `json:"date"` // Last calendar day of the fiscal period
ReportDate time.Time `json:"reportDate"` // When earnings were / will be reported
ReportTime string `json:"reportTime"` // When during the day (before/after market, during hours)
Currency string `json:"currency"` // Currency of the report (may be empty for future earnings)
ReportedEPS *float64 `json:"reportedEPS"` // Actual reported EPS (nil for future earnings)
EstimatedEPS *float64 `json:"estimatedEPS"` // Consensus analyst estimate
SurpriseEPS *float64 `json:"surpriseEPS"` // Difference between reported and estimated
SurpriseEPSPercent *float64 `json:"surpriseEPSpct"` // Surprise as a percentage
Updated time.Time `json:"updated"` // When this earnings data was last updated
}
```
Represents a single earnings report, one per fiscal quarter. Timestamps are normalized to US Eastern time.
### Fields
- `Symbol` — the stock ticker symbol.
- `FiscalYear` / `FiscalQuarter` — the fiscal year and quarter (1-4) of the report.
- `Date` — the last calendar day of the fiscal period.
- `ReportDate` — the date the earnings were or will be reported.
- `ReportTime` — when during the day the report is/was released (before market, after market, or during hours).
- `Currency` — the currency of the report; may be empty for future earnings.
- `ReportedEPS` — the actual reported EPS; `nil` for earnings not yet reported.
- `EstimatedEPS` — the consensus analyst estimate; `nil` when unavailable.
- `SurpriseEPS` — the difference between reported and estimated EPS; `nil` when unavailable.
- `SurpriseEPSPercent` — the surprise as a percentage (JSON alias `surpriseEPSpct`); `nil` when unavailable.
- `Updated` — when this data was last updated.
### Helper Methods
- `String() string` — a one-line summary of the report (EPS fields render as `n/a` when `nil`).
---
# News (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/stocks/news/index.md
Retrieve news articles for a stock symbol.
> [!CAUTION]
> **[Beta Endpoint]**
>
> The news endpoint is currently in beta. Features and response formats may change.
## Making Requests
Both call styles live on the `Stocks` service (`client.Stocks`). Use `GetNews` for the simplest call, or `News` when you also need the raw response and rate-limit metadata.
| Method | Return | Description |
|----------------------------------|----------------------------------------------|--------------------------------------------------------------------------------------|
| **`GetNews(symbol, opts...)`** | `([]NewsArticle, error)` | Convenience wrapper; uses `context.Background()` and discards the response metadata. |
| **`News(ctx, symbol, opts...)`** | `([]NewsArticle, *response.Response, error)` | Context-aware; also returns the raw response + rate-limit metadata. |
## News
```go
func (s *Service) News(ctx context.Context, symbol string, opts ...NewsOption) ([]NewsArticle, *response.Response, error)
func (s *Service) GetNews(symbol string, opts ...NewsOption) ([]NewsArticle, error)
```
Fetch news articles for one symbol. With no options the API returns its default set of recent articles.
### Parameters
- `ctx` (`context.Context`) — controls cancellation and deadlines (context method only).
- `symbol` (`string`) — the stock ticker symbol, e.g. `"AAPL"`. Required; an empty string returns a validation error without making a request.
- Options (`stocks.NewsOption`):
- `stocks.WithNewsWindow(w DateWindow)` — set the publication date range as a single `DateWindow` value. Build the window with any of the shared [date-window constructors](#date-windows), for example `stocks.OnDate(d)` or `stocks.Between(from, to)`.
### Returns
- `[]NewsArticle` — the matching articles.
- `*response.Response` — raw response + rate-limit metadata (context method only).
- `error` — non-nil on request/validation failure.
### Notes
- Only the calendar date of each `time.Time` in a window is used; the time-of-day and zone are ignored.
- If the API responds with 404 because no data is available, `News` returns a `nil` slice, a `nil` error, and a non-nil `*response.Response` whose `NoData` field is `true`.
### Get (simple)
```go
package main
import (
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
articles, err := client.Stocks.GetNews("AAPL")
if err != nil {
log.Fatal(err)
}
for _, a := range articles {
fmt.Printf("%s %s\n", a.PublicationDate.Format("2006-01-02"), a.Headline)
fmt.Printf(" %s\n", a.Source)
}
}
```
**Output**
```
2024-01-25 Apple Reports Record Q1 Earnings
https://www.reuters.com/technology/...
2024-01-24 Apple Unveils New Product Lineup
https://www.bloomberg.com/...
```
### Context + Response
```go
package main
import (
"context"
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
articles, resp, err := client.Stocks.News(ctx, "AAPL")
if err != nil {
log.Fatal(err)
}
if resp.NoData {
fmt.Println("no news available")
return
}
fmt.Printf("Found %d articles\n", len(articles))
for _, a := range articles {
fmt.Println(a) // NewsArticle implements Stringer
}
fmt.Printf("Requests remaining: %d\n", resp.RateLimit.Remaining)
}
```
### With options
```go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
"github.com/MarketDataApp/sdk-go/v2/marketdata/stocks"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
from := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
to := time.Date(2024, 1, 31, 0, 0, 0, 0, time.UTC)
articles, _, err := client.Stocks.News(ctx, "AAPL",
stocks.WithNewsWindow(stocks.Between(from, to)),
)
if err != nil {
log.Fatal(err)
}
for _, a := range articles {
fmt.Printf("- %s (%s)\n", a.Headline, a.PublicationDate.Format("2006-01-02"))
}
}
```
## Date Windows
`stocks.WithNewsWindow` takes a `DateWindow` — the same sealed-union value used by [Candles](https://www.marketdata.app/docs/sdk/go/stocks/candles) and [Earnings](https://www.marketdata.app/docs/sdk/go/stocks/earnings). Build one with exactly one constructor:
| Constructor | Selects | API parameters |
|----------------------------|--------------------------------------|---------------------|
| `stocks.OnDate(d)` | a single calendar day | `date=d` |
| `stocks.Between(from, to)` | an explicit closed range (inclusive) | `from=from&to=to` |
| `stocks.Since(from)` | everything from a day onward | `from=from` |
| `stocks.Until(to)` | up to and including a day | `to=to` |
| `stocks.LastN(n)` | the `n` most recent articles | `countback=n` |
| `stocks.LastNUntil(n, to)` | the `n` articles ending at a day | `countback=n&to=to` |
## NewsArticle
```go
type NewsArticle struct {
Symbol string `json:"symbol"` // Stock ticker symbol
Headline string `json:"headline"` // Article headline
Content string `json:"content"` // Article content (may be partial)
Source string `json:"source"` // URL where the article was published
PublicationDate time.Time `json:"publicationDate"` // When the article was published (US Eastern)
}
```
Represents a single news article about a stock. Timestamps are normalized to US Eastern time.
### Fields
- `Symbol` — the stock ticker symbol the article relates to.
- `Headline` — the article headline/title.
- `Content` — the article content, which may be partial and may include captions or copyright notices.
- `Source` — the URL where the article was published.
- `PublicationDate` — when the article was published.
### Helper Methods
- `String() string` — a one-line summary of the article.
---
# Prices (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/stocks/prices/index.md
Get real-time SmartMid midpoint prices for one or more stocks — a lightweight alternative to a full quote when only a single price per symbol is needed.
## Making Requests
Both call styles live on the `Stocks` service (`client.Stocks`). Use `GetPrices` for the simplest call, or `Prices` when you need functional options or the raw response and rate-limit metadata.
| Method | Return | Description |
|-------------------------------------|----------------------------------------|------------------------------------------------------------------------------------------------------------|
| **`GetPrices(symbols...)`** | `([]Price, error)` | Convenience wrapper; takes symbols variadically, uses `context.Background()`, and does not accept options. |
| **`Prices(ctx, symbols, opts...)`** | `([]Price, *response.Response, error)` | Context-aware; accepts options and also returns the raw response + rate-limit metadata. |
## Prices
```go
func (s *Service) Prices(ctx context.Context, symbols []string, opts ...PriceOption) ([]Price, *response.Response, error)
func (s *Service) GetPrices(symbols ...string) ([]Price, error)
```
Fetch SmartMid midpoint prices for one or many symbols.
### Parameters
- `ctx` (`context.Context`) — controls cancellation and deadlines (context method only).
- `symbols` (`[]string` for `Prices`, variadic `...string` for `GetPrices`) — the stock ticker symbols, e.g. `[]string{"AAPL", "MSFT", "GOOG"}`. At least one symbol is required; an empty set returns a validation error without making a request.
- Options (`stocks.PriceOption`, `Prices` only — `GetPrices` accepts no options):
- `stocks.WithPriceExtended(extended bool)` — control whether prices include extended-hours data. When omitted, the parameter is not sent and the API default applies.
### Returns
- `[]Price` — one `Price` per symbol.
- `*response.Response` — raw response + rate-limit metadata (context method only).
- `error` — non-nil on request/validation failure.
### Notes
- The SmartMid model is Market Data's derived midpoint between bid and ask.
- For a single symbol the SDK uses the path form `stocks/prices/{symbol}/`; for multiple symbols it uses the query form `stocks/prices/?symbols=...`.
- To pass options and still request multiple symbols, use `Prices` — `GetPrices` is options-free by design.
- If the API responds with 404 because no data is available, `Prices` returns a `nil` slice, a `nil` error, and a non-nil `*response.Response` whose `NoData` field is `true`.
### Get (simple)
```go
package main
import (
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
prices, err := client.Stocks.GetPrices("AAPL", "MSFT", "GOOG")
if err != nil {
log.Fatal(err)
}
for _, p := range prices {
fmt.Printf("%s: $%.2f (%.2f%%)\n", p.Symbol, p.Mid, p.ChangePercent)
}
}
```
**Output**
```
AAPL: $186.19 (0.71%)
MSFT: $380.55 (1.25%)
GOOG: $143.12 (-0.50%)
```
### Context + Response
```go
package main
import (
"context"
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
prices, resp, err := client.Stocks.Prices(ctx, []string{"AAPL", "MSFT", "GOOG"})
if err != nil {
log.Fatal(err)
}
if resp.NoData {
fmt.Println("no prices available")
return
}
for _, p := range prices {
fmt.Println(p) // Price implements Stringer
}
fmt.Printf("Requests remaining: %d\n", resp.RateLimit.Remaining)
}
```
### With options
```go
package main
import (
"context"
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
"github.com/MarketDataApp/sdk-go/v2/marketdata/stocks"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
// Regular-session prices only.
prices, _, err := client.Stocks.Prices(ctx, []string{"AAPL", "MSFT"},
stocks.WithPriceExtended(false),
)
if err != nil {
log.Fatal(err)
}
for _, p := range prices {
fmt.Printf("%s: $%.2f (regular session)\n", p.Symbol, p.Mid)
}
}
```
## Price
```go
type Price struct {
Symbol string `json:"symbol"` // Stock ticker symbol
Mid float64 `json:"mid"` // SmartMid midpoint price
Change float64 `json:"change"` // Price change from previous close
ChangePercent float64 `json:"changepct"` // Percentage change from previous close
Updated time.Time `json:"updated"` // When this price was last updated (US Eastern)
}
```
Represents a SmartMid midpoint price for a stock. It is a lightweight alternative to [`Quote`](https://www.marketdata.app/docs/sdk/go/stocks/quote) when only a single price per symbol is needed. Timestamps are normalized to US Eastern time.
### Fields
- `Symbol` — the stock ticker symbol.
- `Mid` — the SmartMid midpoint price.
- `Change` — price change in dollars from the previous close.
- `ChangePercent` — the change from the previous close (JSON alias `changepct`).
- `Updated` — timestamp of the price.
### Helper Methods
- `String() string` — a one-line summary of the price.
---
# Stock Quote (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/stocks/quote/index.md
Get a real-time price quote for a single stock symbol, including bid/ask, last trade, change, and volume.
## Making Requests
Both call styles live on the `Stocks` service (`client.Stocks`). Use `GetQuote` for the simplest call, or `Quote` when you also need the raw response and rate-limit metadata.
| Method | Return | Description |
|-----------------------------------|---------------------------------------|--------------------------------------------------------------------------------------|
| **`GetQuote(symbol, opts...)`** | `(*Quote, error)` | Convenience wrapper; uses `context.Background()` and discards the response metadata. |
| **`Quote(ctx, symbol, opts...)`** | `(*Quote, *response.Response, error)` | Context-aware; also returns the raw response + rate-limit metadata. |
## Quote
```go
func (s *Service) Quote(ctx context.Context, symbol string, opts ...QuoteOption) (*Quote, *response.Response, error)
func (s *Service) GetQuote(symbol string, opts ...QuoteOption) (*Quote, error)
```
Fetch a real-time quote for one stock symbol.
### Parameters
- `ctx` (`context.Context`) — controls cancellation and deadlines (context method only).
- `symbol` (`string`) — the stock ticker symbol, e.g. `"AAPL"`. Required; an empty string returns a validation error without making a request.
- Options (`stocks.QuoteOption`):
- `stocks.WithFiftyTwoWeek(enabled bool)` — request 52-week high/low data, populating the `FiftyTwoWeekHigh` and `FiftyTwoWeekLow` fields. Defaults to off; passing `false` is equivalent to omitting the option.
- `stocks.WithExtended(extended bool)` — control whether the quote includes extended-hours (pre-market/after-hours) data. When omitted, the parameter is not sent and the API default applies. Pass `true` to return the most recent quote regardless of session, or `false` to restrict to the primary session.
### Returns
- `*Quote` — the quote for the requested symbol.
- `*response.Response` — raw response + rate-limit metadata (context method only).
- `error` — non-nil on request/validation failure.
### Notes
- If the API responds with 404 because no data is available, `Quote` returns a `nil` quote, a `nil` error, and a non-nil `*response.Response` whose `NoData` field is `true`. `GetQuote` discards that metadata, so a no-data result is simply a `nil` quote with a `nil` error.
- If the API reports success but returns no quote for the symbol, `Quote` returns a `*stocks.QuoteNotFoundError` (detect it with `errors.As`).
### Get (simple)
```go
package main
import (
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
quote, err := client.Stocks.GetQuote("AAPL")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Symbol: %s\n", quote.Symbol)
fmt.Printf("Last: $%.2f\n", quote.Last)
fmt.Printf("Bid: $%.2f x %d\n", quote.Bid, quote.BidSize)
fmt.Printf("Ask: $%.2f x %d\n", quote.Ask, quote.AskSize)
fmt.Printf("Change: $%.2f (%.2f%%)\n", quote.Change, quote.ChangePercent)
fmt.Printf("Volume: %d\n", quote.Volume)
}
```
**Output**
```
Symbol: AAPL
Last: $186.19
Bid: $186.18 x 200
Ask: $186.20 x 300
Change: $1.05 (0.57%)
Volume: 48234500
```
### Context + Response
```go
package main
import (
"context"
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
quote, resp, err := client.Stocks.Quote(ctx, "AAPL")
if err != nil {
log.Fatal(err)
}
if resp.NoData {
fmt.Println("no quote available")
return
}
fmt.Println(quote) // Quote implements Stringer
// Inspect rate-limit metadata carried on the response.
fmt.Printf("Requests remaining: %d\n", resp.RateLimit.Remaining)
}
```
**Output**
```
AAPL Last: $186.19 Bid: 186.18 (200) Ask: 186.20 (300) Mid: 186.19 Chg: 1.05 (0.57%) Vol: 48234500 Updated: 2024-01-15 16:00:00
Requests remaining: 99998
```
### With options
```go
package main
import (
"context"
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
"github.com/MarketDataApp/sdk-go/v2/marketdata/stocks"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
// 52-week data, regular session only.
quote, _, err := client.Stocks.Quote(ctx, "AAPL",
stocks.WithFiftyTwoWeek(true),
stocks.WithExtended(false),
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s: $%.2f\n", quote.Symbol, quote.Last)
fmt.Printf("52-week high: $%.2f\n", quote.FiftyTwoWeekHigh)
fmt.Printf("52-week low: $%.2f\n", quote.FiftyTwoWeekLow)
}
```
**Output**
```
AAPL: $186.19
52-week high: $199.62
52-week low: $164.08
```
## Quote
```go
type Quote struct {
Symbol string `json:"symbol"` // Stock ticker symbol
Ask float64 `json:"ask"` // Current ask price
AskSize int `json:"askSize"` // Size of the ask
Bid float64 `json:"bid"` // Current bid price
BidSize int `json:"bidSize"` // Size of the bid
Mid float64 `json:"mid"` // Midpoint between bid and ask
Last float64 `json:"last"` // Last trade price
Change float64 `json:"change"` // Price change from previous close
ChangePercent float64 `json:"changepct"` // Percentage change from previous close
Volume int64 `json:"volume"` // Trading volume
Updated time.Time `json:"updated"` // When the quote was last updated
FiftyTwoWeekHigh float64 `json:"52weekHigh,omitempty"` // 52-week high (WithFiftyTwoWeek)
FiftyTwoWeekLow float64 `json:"52weekLow,omitempty"` // 52-week low (WithFiftyTwoWeek)
}
```
Represents a real-time stock quote. Timestamps are normalized to US Eastern time. `FiftyTwoWeekHigh` and `FiftyTwoWeekLow` are populated only when the quote was requested with `stocks.WithFiftyTwoWeek(true)`.
### Fields
- `Symbol` — the stock ticker symbol.
- `Ask` / `AskSize` — the current ask price and the number of shares offered at it.
- `Bid` / `BidSize` — the current bid price and the number of shares bid at it.
- `Mid` — the midpoint between bid and ask.
- `Last` — the last traded price.
- `Change` — price change in dollars from the previous close.
- `ChangePercent` — the change from the previous close (JSON alias `changepct`).
- `Volume` — trading volume for the session.
- `Updated` — timestamp of the quote (US Eastern).
- `FiftyTwoWeekHigh` / `FiftyTwoWeekLow` — 52-week high/low, present only with `WithFiftyTwoWeek`.
### Helper Methods
- `Spread() float64` — the bid-ask spread (`Ask - Bid`).
- `SpreadPercent() float64` — the spread as a percentage of `Mid`.
- `String() string` — a one-line summary of the quote.
---
# Stock Quotes (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/stocks/quotes/index.md
Get real-time price quotes for multiple stock symbols in a single API request.
## Making Requests
Both call styles live on the `Stocks` service (`client.Stocks`). Use `GetQuotes` for the simplest call, or `Quotes` when you need functional options or the raw response and rate-limit metadata.
| Method | Return | Description |
|-------------------------------------|----------------------------------------|------------------------------------------------------------------------------------------------------------|
| **`GetQuotes(symbols...)`** | `([]Quote, error)` | Convenience wrapper; takes symbols variadically, uses `context.Background()`, and does not accept options. |
| **`Quotes(ctx, symbols, opts...)`** | `([]Quote, *response.Response, error)` | Context-aware; accepts options and also returns the raw response + rate-limit metadata. |
## Quotes
```go
func (s *Service) Quotes(ctx context.Context, symbols []string, opts ...QuoteOption) ([]Quote, *response.Response, error)
func (s *Service) GetQuotes(symbols ...string) ([]Quote, error)
```
Fetch real-time quotes for many symbols at once.
### Parameters
- `ctx` (`context.Context`) — controls cancellation and deadlines (context method only).
- `symbols` (`[]string` for `Quotes`, variadic `...string` for `GetQuotes`) — the stock ticker symbols, e.g. `[]string{"AAPL", "MSFT", "GOOG"}`. At least one symbol is required; an empty set returns a validation error without making a request.
- Options (`stocks.QuoteOption`, `Quotes` only — `GetQuotes` accepts no options):
- `stocks.WithFiftyTwoWeek(enabled bool)` — request 52-week high/low data for each quote. Defaults to off.
- `stocks.WithExtended(extended bool)` — control whether quotes include extended-hours data. When omitted, the parameter is not sent and the API default applies.
### Returns
- `[]Quote` — one `Quote` per symbol returned by the API.
- `*response.Response` — raw response + rate-limit metadata (context method only).
- `error` — non-nil on request/validation failure.
### Notes
- If the API responds with 404 because no data is available, `Quotes` returns a `nil` slice, a `nil` error, and a non-nil `*response.Response` whose `NoData` field is `true`. `GetQuotes` discards that metadata, so a no-data result is simply a `nil` slice with a `nil` error.
- To pass options and still request multiple symbols, use `Quotes` — `GetQuotes` is options-free by design.
### Get (simple)
```go
package main
import (
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
quotes, err := client.Stocks.GetQuotes("AAPL", "MSFT", "GOOG")
if err != nil {
log.Fatal(err)
}
for _, q := range quotes {
fmt.Printf("%s: $%.2f (%.2f%%)\n", q.Symbol, q.Last, q.ChangePercent)
}
}
```
**Output**
```
AAPL: $186.19 (0.57%)
MSFT: $380.55 (0.61%)
GOOG: $143.12 (-0.31%)
```
### Context + Response
```go
package main
import (
"context"
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
quotes, resp, err := client.Stocks.Quotes(ctx, []string{"AAPL", "MSFT", "GOOG"})
if err != nil {
log.Fatal(err)
}
if resp.NoData {
fmt.Println("no quotes available")
return
}
for _, q := range quotes {
fmt.Println(q) // Quote implements Stringer
}
fmt.Printf("Requests remaining: %d\n", resp.RateLimit.Remaining)
}
```
### With options
```go
package main
import (
"context"
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
"github.com/MarketDataApp/sdk-go/v2/marketdata/stocks"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
watchlist := []string{"AAPL", "MSFT", "GOOG"}
quotes, _, err := client.Stocks.Quotes(ctx, watchlist,
stocks.WithFiftyTwoWeek(true),
)
if err != nil {
log.Fatal(err)
}
for _, q := range quotes {
pctFromHigh := (q.Last - q.FiftyTwoWeekHigh) / q.FiftyTwoWeekHigh * 100
fmt.Printf("%-5s $%8.2f 52wk %.2f-%.2f (%+.1f%% from high)\n",
q.Symbol, q.Last, q.FiftyTwoWeekLow, q.FiftyTwoWeekHigh, pctFromHigh)
}
}
```
## Quote
`Quotes` returns a slice of the same `Quote` value used by the single-symbol [Quote](https://www.marketdata.app/docs/sdk/go/stocks/quote) endpoint.
```go
type Quote struct {
Symbol string `json:"symbol"` // Stock ticker symbol
Ask float64 `json:"ask"` // Current ask price
AskSize int `json:"askSize"` // Size of the ask
Bid float64 `json:"bid"` // Current bid price
BidSize int `json:"bidSize"` // Size of the bid
Mid float64 `json:"mid"` // Midpoint between bid and ask
Last float64 `json:"last"` // Last trade price
Change float64 `json:"change"` // Price change from previous close
ChangePercent float64 `json:"changepct"` // Percentage change from previous close
Volume int64 `json:"volume"` // Trading volume
Updated time.Time `json:"updated"` // When the quote was last updated
FiftyTwoWeekHigh float64 `json:"52weekHigh,omitempty"` // 52-week high (WithFiftyTwoWeek)
FiftyTwoWeekLow float64 `json:"52weekLow,omitempty"` // 52-week low (WithFiftyTwoWeek)
}
```
### Fields
- `Symbol` — the stock ticker symbol.
- `Ask` / `AskSize` — the current ask price and the number of shares offered at it.
- `Bid` / `BidSize` — the current bid price and the number of shares bid at it.
- `Mid` — the midpoint between bid and ask.
- `Last` — the last traded price.
- `Change` — price change in dollars from the previous close.
- `ChangePercent` — the change from the previous close (JSON alias `changepct`).
- `Volume` — trading volume for the session.
- `Updated` — timestamp of the quote (US Eastern).
- `FiftyTwoWeekHigh` / `FiftyTwoWeekLow` — 52-week high/low, present only with `WithFiftyTwoWeek`.
### Helper Methods
- `Spread() float64` — the bid-ask spread (`Ask - Bid`).
- `SpreadPercent() float64` — the spread as a percentage of `Mid`.
- `String() string` — a one-line summary of the quote.
---
# Utilities (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/utilities/index.md
Report on the Market Data API itself rather than on market data: check API health and uptime, echo back your request headers for debugging, and inspect the authenticated account's API credit state. The Go SDK exposes these endpoints through the `client.Utilities` service.
## Utilities Endpoints
---
# API Status (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/utilities/api-status/index.md
Check the current status of the Market Data API and its historical uptime.
## Making Requests
Fetch API status with the `Status` method on the `client.Utilities` service. It takes no parameters and no options, and comes in two call styles:
| Method | Return | Description |
|-------------------|-----------------------------------------------------|------------------------------------------------------------------------|
| **`GetStatus()`** | `(*utilities.APIStatus, error)` | Convenience; uses a background context and discards response metadata. |
| **`Status(ctx)`** | `(*utilities.APIStatus, *response.Response, error)` | Context-aware; also returns the raw response plus rate-limit metadata. |
## Status
```go
func (s *Service) Status(ctx context.Context) (*utilities.APIStatus, *response.Response, error)
func (s *Service) GetStatus() (*utilities.APIStatus, error)
```
Get the current status of the Market Data API from the unversioned `/status/` endpoint. The API reports per-service status; the returned [`APIStatus`](#apistatus) aggregates it, with `Status` set to `"online"` only when every service is online. The status is updated every 5 minutes and remains accessible even when the API is experiencing issues.
### Parameters
- `ctx` (`context.Context`) — request context for cancellation and deadlines (context method only).
This endpoint takes no functional options.
### Returns
- `*utilities.APIStatus` — the aggregated [`APIStatus`](#apistatus).
- `*response.Response` — raw response plus rate-limit metadata (context method only).
- `error` — non-nil on request or decoding failure.
### Notes
- This endpoint is public and continues to respond even when the API is offline.
- In the unlikely event the endpoint responds `404`, both call styles return a `nil` `APIStatus` and a `nil` error; with the context method the returned `*response.Response` has its `NoData` field set to `true`.
### Get (simple)
```go
package main
import (
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient() // reads MARKETDATA_TOKEN from env / .env
if err != nil {
log.Fatal(err)
}
defer client.Close()
status, err := client.Utilities.GetStatus()
if err != nil {
log.Fatal(err)
}
fmt.Println(status)
}
```
**Output**
```
API online (30d: 99.98% 90d: 99.95%) Updated: 2026-07-12 09:30:00
```
### Context + Response
```go
package main
import (
"context"
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
status, resp, err := client.Utilities.Status(context.Background())
if err != nil {
log.Fatal(err)
}
if resp.NoData || status == nil {
fmt.Println("Status unavailable.")
return
}
if status.IsOnline() {
fmt.Println("API is online!")
}
fmt.Printf("30-day uptime: %.2f%%\n", status.Uptime30d)
fmt.Printf("90-day uptime: %.2f%%\n", status.Uptime90d)
}
```
**Output**
```
API is online!
30-day uptime: 99.98%
90-day uptime: 99.95%
```
## APIStatus
```go
type APIStatus struct {
Status string `json:"s"` // Status indicates if the API is online.
Uptime30d float64 `json:"uptime30d"` // Uptime30d is the 30-day uptime percentage.
Uptime90d float64 `json:"uptime90d"` // Uptime90d is the 90-day uptime percentage.
Updated time.Time `json:"updated"` // Updated is when the status was last checked.
}
```
`APIStatus` represents the aggregated status of the Market Data API. `Status` is `"online"` only when every underlying service is online, otherwise `"offline"`. `Uptime30d` and `Uptime90d` are the average 30-day and 90-day uptime percentages across services.
### Fields
- `Status` (`string`) — `"online"` or `"offline"`.
- `Uptime30d` (`float64`) — the 30-day uptime percentage.
- `Uptime90d` (`float64`) — the 90-day uptime percentage.
- `Updated` (`time.Time`) — when the status was last checked, normalized to US Eastern.
### Methods
- `String() string` — a one-line summary, e.g. `API online (30d: 99.98% 90d: 99.95%) Updated: 2026-07-12 09:30:00`.
- `IsOnline() bool` — reports whether the API is currently online (`Status == "online"`).
---
# Headers (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/utilities/headers/index.md
Echo back the request headers your application is sending — useful for debugging authentication and proxy issues.
## Making Requests
Fetch your request headers with the `Headers` method on the `client.Utilities` service. It takes no parameters and no options, and comes in two call styles:
| Method | Return | Description |
|--------------------|---------------------------------------------------|------------------------------------------------------------------------|
| **`GetHeaders()`** | `(*utilities.Headers, error)` | Convenience; uses a background context and discards response metadata. |
| **`Headers(ctx)`** | `(*utilities.Headers, *response.Response, error)` | Context-aware; also returns the raw response plus rate-limit metadata. |
## Headers
```go
func (s *Service) Headers(ctx context.Context) (*utilities.Headers, *response.Response, error)
func (s *Service) GetHeaders() (*utilities.Headers, error)
```
Get the request headers your application is sending, echoed back by the unversioned `/headers/` endpoint. The returned [`Headers`](#headers-type) maps header names to the values the server received, which is useful for debugging authentication and proxy problems.
### Parameters
- `ctx` (`context.Context`) — request context for cancellation and deadlines (context method only).
This endpoint takes no functional options.
### Returns
- `*utilities.Headers` — the echoed [`Headers`](#headers-type).
- `*response.Response` — raw response plus rate-limit metadata (context method only).
- `error` — non-nil on request or decoding failure.
### Notes
- Sensitive header values (such as `Authorization`) are partially redacted by the server for security.
- In the unlikely event the endpoint responds `404`, both call styles return a `nil` result and a `nil` error; with the context method the returned `*response.Response` has its `NoData` field set to `true`.
### Get (simple)
```go
package main
import (
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient() // reads MARKETDATA_TOKEN from env / .env
if err != nil {
log.Fatal(err)
}
defer client.Close()
headers, err := client.Utilities.GetHeaders()
if err != nil {
log.Fatal(err)
}
if _, ok := headers.Headers["Authorization"]; ok {
fmt.Println("Authorization header is being sent.")
} else {
fmt.Println("Authorization header is missing — check MARKETDATA_TOKEN.")
}
}
```
**Output**
```
Authorization: Bearer 1AB...***REDACTED***
Accept-Encoding: gzip
User-Agent: sdk-go/2.0.0
Host: api.marketdata.app
```
### Context + Response
```go
package main
import (
"context"
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
headers, resp, err := client.Utilities.Headers(context.Background())
if err != nil {
log.Fatal(err)
}
if resp.NoData || headers == nil {
fmt.Println("No headers returned.")
return
}
for name, value := range headers.Headers {
fmt.Printf("%s: %s\n", name, value)
}
}
```
**Output**
```
Authorization: Bearer 1AB...***REDACTED***
Accept-Encoding: gzip
User-Agent: sdk-go/2.0.0
Host: api.marketdata.app
```
## Headers
```go
type Headers struct {
Headers map[string]string `json:"headers"` // Headers maps header names to values.
}
```
`Headers` represents the request headers echoed back by the API. It is useful for debugging authentication issues.
### Fields
- `Headers` (`map[string]string`) — a map of header name to the value the server received.
### Methods
- `String() string` — a concise summary of all header name/value pairs, e.g. `Headers{Authorization: Bearer 1AB...***REDACTED***, Host: api.marketdata.app}`.
---
# User (Go SDK)
Source: https://www.marketdata.app/docs/sdk/go/utilities/user/index.md
Retrieve information about the authenticated account: its API credit state for the current window and its options data entitlement.
## Making Requests
Fetch account information with the `User` method on the `client.Utilities` service. It takes no parameters and no options, but requires a valid API token. It comes in two call styles:
| Method | Return | Description |
|-----------------|----------------------------------------------------|------------------------------------------------------------------------|
| **`GetUser()`** | `(*utilities.UserInfo, error)` | Convenience; uses a background context and discards response metadata. |
| **`User(ctx)`** | `(*utilities.UserInfo, *response.Response, error)` | Context-aware; also returns the raw response plus rate-limit metadata. |
## User
```go
func (s *Service) User(ctx context.Context) (*utilities.UserInfo, *response.Response, error)
func (s *Service) GetUser() (*utilities.UserInfo, error)
```
Get information about the authenticated user and their account from the unversioned `/user/` endpoint. The returned [`UserInfo`](#userinfo) carries the account's API credit state for the current window — taken from the `x-api-ratelimit-*` response headers — and the account's options data entitlement, taken from the response body.
### Parameters
- `ctx` (`context.Context`) — request context for cancellation and deadlines (context method only).
This endpoint takes no functional options, but it requires a valid API token.
### Returns
- `*utilities.UserInfo` — the account's [`UserInfo`](#userinfo).
- `*response.Response` — raw response plus rate-limit metadata (context method only).
- `error` — non-nil on request or decoding failure.
### Notes
- Rate limits track **credits**. Most requests consume 1 credit, but bulk or options requests may consume several.
- In the unlikely event the endpoint responds `404`, both call styles return a `nil` result and a `nil` error; with the context method the returned `*response.Response` has its `NoData` field set to `true`.
### Get (simple)
```go
package main
import (
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient() // reads MARKETDATA_TOKEN from env / .env
if err != nil {
log.Fatal(err)
}
defer client.Close()
user, err := client.Utilities.GetUser()
if err != nil {
log.Fatal(err)
}
fmt.Println(user)
}
```
**Output**
```
User{Credits: 99847/100000, Resets: 2026-07-13 00:00:00, Options: OPRA data delayed 15 minutes}
```
### Context + Response
```go
package main
import (
"context"
"fmt"
"log"
"github.com/MarketDataApp/sdk-go/v2/marketdata"
)
func main() {
client, err := marketdata.NewClient(marketdata.WithToken("YOUR_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer client.Close()
user, resp, err := client.Utilities.User(context.Background())
if err != nil {
log.Fatal(err)
}
if resp.NoData || user == nil {
fmt.Println("No account information returned.")
return
}
fmt.Printf("Credits: %d/%d\n", user.CreditsRemaining, user.CreditLimit)
fmt.Printf("Consumed by this request: %d\n", user.CreditsConsumed)
fmt.Printf("Window resets at: %s\n", user.ResetAt.Format("2006-01-02 15:04:05"))
fmt.Printf("Options data: %s\n", user.OptionsDataPermissions)
}
```
**Output**
```
Credits: 99847/100000
Consumed by this request: 1
Window resets at: 2026-07-13 00:00:00
Options data: OPRA data delayed 15 minutes
```
## UserInfo
```go
type UserInfo struct {
CreditLimit int // Total API credit allowance for the current window.
CreditsRemaining int // API credits left in the current window.
CreditsConsumed int // API credits consumed by the user info request itself.
ResetAt time.Time // When the current credit window resets.
OptionsDataPermissions string // The account's options data entitlement.
}
```
`UserInfo` describes the authenticated account: the API credit state for the current window (taken from the `x-api-ratelimit-*` response headers) and the account's options data entitlement (taken from the response body).
### Fields
- `CreditLimit` (`int`) — the account's total API credit allowance for the current window.
- `CreditsRemaining` (`int`) — the number of API credits left in the current window.
- `CreditsConsumed` (`int`) — the number of API credits consumed by the user info request itself (not cumulative).
- `ResetAt` (`time.Time`) — when the current credit window resets.
- `OptionsDataPermissions` (`string`) — the account's options data entitlement, e.g. `"OPRA data delayed 15 minutes"`.
### Methods
- `String() string` — a one-line summary, e.g. `User{Credits: 99847/100000, Resets: 2026-07-13 00:00:00, Options: OPRA data delayed 15 minutes}`.
---
# Java SDK
Source: https://www.marketdata.app/docs/sdk/java/index.md
Welcome to the Market Data Java SDK documentation. This SDK lets you integrate Market Data services into any JVM application. It ships typed, records-based responses, sync **and** async (`CompletableFuture`) variants for every endpoint, automatic retries and rate-limit tracking, and a sealed exception hierarchy you can branch on exhaustively.
The SDK is written in Java (JDK 17+) with no Kotlin runtime dependency, but it is designed to be **idiomatic from Kotlin too**: nullability is annotated (no platform types), the client is `AutoCloseable` (so `use {}` works), and async methods return `CompletableFuture`, which Kotlin interops with directly.
## Quick Start
### Java
```java
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.stocks.StockQuoteRequest;
// The no-arg constructor reads MARKETDATA_TOKEN from the environment (or a .env file)
// and validates it on startup. The client is AutoCloseable.
try (MarketDataClient client = new MarketDataClient()) {
// A single quote — quote(...) returns a list; a single symbol is row 0.
var quote = client.stocks().quote(StockQuoteRequest.of("AAPL")).values().get(0);
System.out.println(quote.symbol() + " last=" + quote.last());
}
```
### Kotlin
```kotlin
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.stocks.StockQuoteRequest
// `use {}` closes the client when the block ends — the Kotlin equivalent of try-with-resources.
MarketDataClient().use { client ->
val quote = client.stocks().quote(StockQuoteRequest.of("AAPL")).values()[0]
println("${quote.symbol()} last=${quote.last()}")
}
```
## Open Source
The SDK is open source and available on GitHub. Feel free to contribute to the project, report bugs, or request new features.
- [Java SDK on GitHub](https://github.com/MarketDataApp/sdk-java/)
## Documentation
The best source for documentation on the SDK is right here. This documentation is the most up-to-date and accurate source of information on the SDK.
## Using the SDK
This SDK is designed to help you get up and running with Market Data's APIs as quickly as possible, providing all the tools you need to access real-time stock and options prices, historical data, and much more.
### Getting Started
1. [Install the SDK](https://www.marketdata.app/docs/sdk/java/installation) into a Gradle or Maven project.
2. Set up your [authentication token](https://www.marketdata.app/docs/sdk/java/authentication) to access the API.
3. Learn about the [client](https://www.marketdata.app/docs/sdk/java/client) and how to make your first API requests.
4. Configure [Settings](https://www.marketdata.app/docs/sdk/java/settings) to customize output format, date format, and other universal parameters.
5. Explore the available endpoints for [stocks](https://www.marketdata.app/docs/sdk/java/stocks), [options](https://www.marketdata.app/docs/sdk/java/options), [funds](https://www.marketdata.app/docs/sdk/java/funds), [markets](https://www.marketdata.app/docs/sdk/java/markets), and [utilities](https://www.marketdata.app/docs/sdk/java/utilities).
### Support
- If you have any questions or need further assistance, please don't hesitate to open a ticket at our [helpdesk](https://www.marketdata.app/dashboard/).
- If you find a bug you may also [open an issue in our GitHub repository](https://github.com/MarketDataApp/sdk-java/issues). Please only open issues if you find a bug. Use our helpdesk for general questions or implementation assistance.
---
# Authentication (Java SDK)
Source: https://www.marketdata.app/docs/sdk/java/authentication/index.md
The Market Data API uses a **Bearer Token** for authentication. The token is required for almost every request. Your token should have been e-mailed to you when you first signed up for an account. If you do not have a token or have lost your sign-up email, request a new token from the [Market Data Dashboard](https://www.marketdata.app/dashboard/).
There are three ways to set your token when using the Java SDK:
1. Set it from an environment variable _(recommended for production)_
2. Load it from a `.env` file _(recommended for local development)_
3. Pass it directly when creating the [client](https://www.marketdata.app/docs/sdk/java/client)
On startup, the SDK looks for the `MARKETDATA_TOKEN` environment variable. If found, it uses that token for all requests. The SDK also loads a `.env` file automatically from the working directory if one is present.
> [!TIP]
> When your code is running in a production environment, we recommend using an environment variable to ensure your token is not stored with your code. This is the most secure way to set your token.
## How To Set Up The Environment Variable
### Set The Environment Variable In The Console
### Mac / Linux
This command sets the environment variable for the current session only. If you open a new terminal or restart your computer, it will not persist.
```bash
export MARKETDATA_TOKEN="your_api_token"
```
#### Make The Variable Persistent
Add the `export` line to your shell's profile script (`~/.zshrc`, `~/.bashrc`, `~/.bash_profile`, etc.), then restart your terminal or run `source ~/.zshrc` (adjusting for your shell).
### Windows
`setx` sets the variable permanently, but it is not available in the current Command Prompt session — open a new one after running it.
```bash
setx MARKETDATA_TOKEN "your_api_token"
```
### Using a .env File
The SDK automatically loads a `.env` file from your working directory at startup. Create a file named `.env` in your project root:
```ini title=".env"
MARKETDATA_TOKEN=your_api_token
```
> [!WARNING]
> Add `.env` to your `.gitignore` so the token is not committed to source control.
### Make A Test Request
Verify your authentication is working by making a test request against `SPY` (or any symbol that requires authentication). Do **not** use `AAPL` to test authentication — `AAPL` is a free test symbol and returns data even when you are not authenticated.
### Java
```java
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.stocks.StockQuoteRequest;
import com.marketdata.sdk.exception.AuthenticationError;
// No need to pass a token here — the SDK reads MARKETDATA_TOKEN automatically.
try (MarketDataClient client = new MarketDataClient()) {
var quote = client.stocks().quote(StockQuoteRequest.of("SPY")).values().get(0);
System.out.println(quote.symbol() + " last=" + quote.last());
} catch (AuthenticationError e) {
System.out.println("Authentication failed: " + e.getMessage());
}
```
### Kotlin
```kotlin
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.stocks.StockQuoteRequest
import com.marketdata.sdk.exception.AuthenticationError
MarketDataClient().use { client ->
try {
val quote = client.stocks().quote(StockQuoteRequest.of("SPY")).values()[0]
println("${quote.symbol()} last=${quote.last()}")
} catch (e: AuthenticationError) {
println("Authentication failed: ${e.message}")
}
}
```
## Passing the Token Directly
If you prefer to pass the token explicitly (not recommended for production code), use the four-argument constructor `(apiKey, baseUrl, apiVersion, validateOnStartup)`. Pass `null` for any slot you want resolved from the cascade or left at its default.
### Java
```java
import com.marketdata.sdk.MarketDataClient;
try (MarketDataClient client =
new MarketDataClient("your_token_here", null, null, true)) {
// ... make requests
}
```
### Kotlin
```kotlin
import com.marketdata.sdk.MarketDataClient
MarketDataClient("your_token_here", null, null, true).use { client ->
// ... make requests
}
```
> [!NOTE]
> **[Demo mode]**
>
> If no token is found anywhere in the cascade, the SDK runs in **demo mode** — startup validation is skipped and you can call the free, public endpoints (such as `AAPL` quotes and `utilities().status()`).
## Next Steps
After successful authentication, read the overview of how the [client](https://www.marketdata.app/docs/sdk/java/client) works, then configure [Settings](https://www.marketdata.app/docs/sdk/java/settings) to customize output format, date format, and other universal parameters.
---
# Client (Java SDK)
Source: https://www.marketdata.app/docs/sdk/java/client/index.md
The `MarketDataClient` is the entry point to the SDK. It handles API requests, response parsing, rate-limit tracking, retries with exponential backoff, and logging. A single client gives you access to all five resources: stocks, options, funds, markets, and utilities.
The client is immutable, thread-safe, and `AutoCloseable`. Create one per application (it holds a shared HTTP client and a 50-request concurrency pool) and close it when you're done.
## Get Started Quickly
1. Review the [authentication documentation](https://www.marketdata.app/docs/sdk/java/authentication) to learn how to set your API token.
2. Create a [`MarketDataClient`](#marketdataclient) and use it to make requests.
3. Reach a resource through `client.stocks()`, `client.options()`, etc.
4. Track your [rate limit](#rate-limits) to see how many API credits remain.
5. Configure [Settings](https://www.marketdata.app/docs/sdk/java/settings) to customize output format, date format, and other universal parameters.
## MarketDataClient
```java
public final class MarketDataClient implements AutoCloseable {
// Production: resolves everything from the configuration cascade and
// validates the token on startup.
public MarketDataClient();
// Full control: any null falls back to the cascade / default for that slot.
public MarketDataClient(
@Nullable String apiKey,
@Nullable String baseUrl, // default: https://api.marketdata.app
@Nullable String apiVersion, // default: v1
boolean validateOnStartup);
// Resources
public StocksResource stocks();
public OptionsResource options();
public FundsResource funds();
public MarketsResource markets();
public UtilitiesResource utilities();
// Latest client-wide rate-limit snapshot (or null before the first request)
public @Nullable RateLimitSnapshot getRateLimits();
// Releases the shared HTTP client
@Override public void close();
}
```
### Constructors
The **no-arg constructor** is what you'll use in production. It resolves the token and settings from the [configuration cascade](https://www.marketdata.app/docs/sdk/java/settings#configuration-cascade) and fires a single `GET /user/` request to validate the token before returning.
The **four-arg constructor** `(apiKey, baseUrl, apiVersion, validateOnStartup)` lets you set everything explicitly — handy for tests, short-lived runtimes, and serverless platforms where you want to skip the startup round-trip (`validateOnStartup = false`).
### Java
```java
import com.marketdata.sdk.MarketDataClient;
// Production: token from the cascade, validated on startup.
try (MarketDataClient client = new MarketDataClient()) {
// ...
}
// Explicit, no startup validation (e.g. on a Lambda cold start):
try (MarketDataClient client =
new MarketDataClient("your_token", null, null, false)) {
// ...
}
```
### Kotlin
```kotlin
import com.marketdata.sdk.MarketDataClient
MarketDataClient().use { client ->
// ...
}
MarketDataClient("your_token", null, null, false).use { client ->
// ...
}
```
> [!NOTE]
> **[Fail-fast configuration]**
>
> A misconfigured base URL or API version is rejected with an `IllegalArgumentException` **at construction**, not later on the first request. Tokens are never printed in full — the client's `toString()` redacts them (long tokens show only the last four characters; short ones are hidden entirely).
## Sync and Async
Every endpoint comes in two variants: a blocking call (e.g. `status()`) and an async one (e.g. `statusAsync()`) returning a `CompletableFuture`. They share the same validation, retry, and rate-limit logic — pick whichever fits your code. Internally the SDK is async-first; the sync method is a thin wrapper that joins the future and unwraps the underlying exception.
Async pays off when you fan out several requests: total time is about the slowest single call, not the sum.
### Java
```java
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.UtilitiesStatusResponse;
import java.util.concurrent.CompletableFuture;
try (MarketDataClient client = new MarketDataClient()) {
// Sync — blocks and returns the typed response.
var sync = client.utilities().status();
System.out.println(sync.values().size() + " services");
// Async — returns immediately with a future. Attach a callback, or join().
CompletableFuture future = client.utilities().statusAsync();
future.thenAccept(resp -> System.out.println(resp.values().size() + " services"));
// Fan out several calls in parallel and wait for all of them.
var a = client.utilities().statusAsync();
var b = client.utilities().statusAsync();
CompletableFuture.allOf(a, b).join();
}
```
### Kotlin
```kotlin
import com.marketdata.sdk.MarketDataClient
import java.util.concurrent.CompletableFuture
MarketDataClient().use { client ->
// Sync
val sync = client.utilities().status()
println("${sync.values().size} services")
// Async — returns a CompletableFuture (coroutine users can await() it
// via kotlinx-coroutines-jdk8).
client.utilities().statusAsync()
.thenAccept { resp -> println("${resp.values().size} services") }
.join()
}
```
### Composing async calls
The point of the async variants is to **stay non-blocking** — chain off the future instead of calling `join()` in the middle. Use `thenApply` to transform a result, `thenCompose` to chain a dependent call, and fire independent calls together when they don't depend on each other.
### Java
```java
import com.marketdata.sdk.options.OptionsLookupRequest;
import com.marketdata.sdk.options.OptionsQuoteRequest;
import com.marketdata.sdk.stocks.StockQuoteRequest;
import java.util.concurrent.CompletableFuture;
// thenApply — transform the result without blocking.
CompletableFuture lastPrice = client.stocks()
.quoteAsync(StockQuoteRequest.of("AAPL"))
.thenApply(resp -> resp.values().get(0).last());
// thenCompose — chain a dependent async call (resolve a symbol, then quote it).
var optionQuote = client.options()
.lookupAsync(OptionsLookupRequest.of("AAPL 1/16/2026 $200 Call"))
.thenCompose(sym -> client.options().quoteAsync(OptionsQuoteRequest.of(sym.values())));
// Independent calls in parallel, then use every result.
var aapl = client.stocks().quoteAsync(StockQuoteRequest.of("AAPL"));
var msft = client.stocks().quoteAsync(StockQuoteRequest.of("MSFT"));
CompletableFuture.allOf(aapl, msft).join(); // wait for both
double spread = aapl.join().values().get(0).last() - msft.join().values().get(0).last();
```
### Kotlin
```kotlin
import com.marketdata.sdk.options.OptionsLookupRequest
import com.marketdata.sdk.options.OptionsQuoteRequest
import com.marketdata.sdk.stocks.StockQuoteRequest
import java.util.concurrent.CompletableFuture
// thenApply — transform the result without blocking.
val lastPrice = client.stocks()
.quoteAsync(StockQuoteRequest.of("AAPL"))
.thenApply { resp -> resp.values()[0].last() }
// thenCompose — chain a dependent async call.
val optionQuote = client.options()
.lookupAsync(OptionsLookupRequest.of("AAPL 1/16/2026 \$200 Call"))
.thenCompose { sym -> client.options().quoteAsync(OptionsQuoteRequest.of(sym.values())) }
// Independent calls in parallel, then use every result.
val aapl = client.stocks().quoteAsync(StockQuoteRequest.of("AAPL"))
val msft = client.stocks().quoteAsync(StockQuoteRequest.of("MSFT"))
CompletableFuture.allOf(aapl, msft).join()
val spread = aapl.join().values()[0].last()!! - msft.join().values()[0].last()!!
```
### Handling errors in async
When a request fails, the future completes **exceptionally**. Inside `exceptionally()`, `handle()`, or `whenComplete()`, the throwable you receive is a `java.util.concurrent.CompletionException` — call `getCause()` to reach the underlying `MarketDataException`. (The blocking variants do this unwrapping for you, which is why `try/catch` around a sync call catches the `MarketDataException` directly.)
### Java
```java
import com.marketdata.sdk.exception.RateLimitError;
import com.marketdata.sdk.stocks.StockQuoteRequest;
import java.util.concurrent.CompletionException;
client.stocks().quoteAsync(StockQuoteRequest.of("AAPL"))
.thenApply(resp -> resp.values().get(0).last())
.exceptionally(ex -> {
// `ex` is a CompletionException; the real cause is the MarketDataException.
Throwable cause = (ex instanceof CompletionException && ex.getCause() != null)
? ex.getCause() : ex;
if (cause instanceof RateLimitError) {
System.out.println("Rate limited — backing off");
}
return null; // fallback value the rest of the chain will see
})
.join();
// handle((value, throwable) -> ...) sees both outcomes in one place; use it when
// you want to map success and failure to the same result type.
```
### Kotlin
```kotlin
import com.marketdata.sdk.exception.RateLimitError
import com.marketdata.sdk.stocks.StockQuoteRequest
import java.util.concurrent.CompletionException
client.stocks().quoteAsync(StockQuoteRequest.of("AAPL"))
.thenApply { resp -> resp.values()[0].last() }
.exceptionally { ex ->
val cause = if (ex is CompletionException && ex.cause != null) ex.cause else ex
if (cause is RateLimitError) println("Rate limited — backing off")
null
}
.join()
```
> [!NOTE]
> **[`join()` vs `get()`]**
>
> To block on a future, prefer **`join()`** — it throws an unchecked `CompletionException`. `get()` does the same thing but throws the checked `ExecutionException` (plus `InterruptedException`), forcing a `try/catch`. Both wrap the underlying cause identically, so `join()` is usually the friendlier choice.
## The Response Object
Every endpoint returns a typed response that wraps the decoded data plus request metadata, with a uniform surface regardless of endpoint or format.
```java
public interface MarketDataResponse {
T values(); // the typed payload (e.g. List)
int statusCode(); // 200, 203, or 404
boolean isNoData(); // true on a 404 "no_data" response
@Nullable String requestId(); // server request id, for support tickets
java.net.URI requestUrl(); // the absolute request URL
@Nullable RateLimitSnapshot rateLimit(); // this request's rate limit
String json(); // the raw response body, as sent
boolean isJson();
boolean isCsv();
boolean isHtml();
void saveToFile(java.nio.file.Path path); // write the raw body verbatim
}
```
```java
var response = client.stocks().quote(StockQuoteRequest.of("AAPL"));
response.values(); // List — the part you usually want
response.statusCode(); // 200
response.requestId(); // e.g. for a support ticket
response.rateLimit(); // this request's rate-limit snapshot (may be null)
response.saveToFile(Path.of("aapl.json")); // cache the raw body
```
## Error Handling
Everything the SDK throws is a `MarketDataException`. It is a **sealed** hierarchy — the seven subtypes below are the complete set, so you can branch on them exhaustively and the compiler will tell you if a future major version adds one. Each exception carries support context: `getStatusCode()`, `getRequestId()`, `getRequestUrl()`, and `getSupportInfo()`.
| Subtype | When it's thrown |
|-----------------------|--------------------------------------------------|
| `AuthenticationError` | Missing or invalid token (HTTP 401) |
| `BadRequestError` | Invalid parameters (HTTP 4xx) |
| `NotFoundError` | The resource doesn't exist (HTTP 404) |
| `RateLimitError` | Quota exceeded (HTTP 429); see `getRetryAfter()` |
| `ServerError` | API-side failure (HTTP 5xx) |
| `NetworkError` | Connection failure or timeout |
| `ParseError` | The response could not be decoded |
Async calls surface the same exceptions through the `CompletableFuture` (wrapped in a `CompletionException`); the sync wrappers unwrap them so you catch the cause directly.
### Java (JDK 17)
```java
import com.marketdata.sdk.exception.*;
try {
var quote = client.stocks().quote(StockQuoteRequest.of("AAPL"));
} catch (AuthenticationError e) {
System.out.println("Check your token");
} catch (RateLimitError e) {
System.out.println("Rate limited — retry after "
+ e.getRetryAfter().map(d -> d.toSeconds() + "s").orElse("a moment"));
} catch (MarketDataException e) {
// Any other case. Attach getSupportInfo() to a bug report.
System.out.println(e.getSupportInfo());
}
```
### Java (JDK 21+)
```java
import com.marketdata.sdk.exception.*;
// On JDK 21+ the sealed hierarchy enables an exhaustive switch — the compiler
// rejects the code if any subtype is left unhandled.
try {
var quote = client.stocks().quote(StockQuoteRequest.of("AAPL"));
} catch (MarketDataException e) {
String message = switch (e) {
case AuthenticationError a -> "Authentication failed — check your token";
case BadRequestError b -> "Bad request — check your parameters";
case NotFoundError n -> "Not found";
case RateLimitError r -> "Rate limited";
case ServerError s -> "Server error (HTTP " + s.getStatusCode() + ")";
case NetworkError n -> "Network problem — is the API reachable?";
case ParseError p -> "Could not parse the response";
};
System.out.println(message);
}
```
### Kotlin
```kotlin
import com.marketdata.sdk.exception.*
try {
val quote = client.stocks().quote(StockQuoteRequest.of("AAPL"))
} catch (e: AuthenticationError) {
println("Check your token")
} catch (e: RateLimitError) {
println("Rate limited")
} catch (e: MarketDataException) {
println(e.supportInfo)
}
```
## Rate Limits
The SDK tracks your rate limit from the `x-api-ratelimit-*` headers on every response.
- **Per-request:** `response.rateLimit()` reflects the headers of that specific response.
- **Client-wide:** `client.getRateLimits()` returns the latest snapshot seen across all requests.
```java
public record RateLimitSnapshot(int limit, int remaining, Instant reset, int consumed) {}
```
```java
var response = client.stocks().quote(StockQuoteRequest.of("AAPL"));
var rl = response.rateLimit();
if (rl != null) {
System.out.println(rl.remaining() + " / " + rl.limit() + " requests left");
}
```
The SDK also fails fast: if a prior response reported `remaining = 0` and the reset window has not yet elapsed, the next request raises a `RateLimitError` before hitting the network.
## Closing the Client
`MarketDataClient` is `AutoCloseable`. Prefer try-with-resources (Java) or `use {}` (Kotlin) so the underlying HTTP client is released. For a long-lived, application-scoped client, call `close()` on shutdown.
---
# Funds (Java SDK)
Source: https://www.marketdata.app/docs/sdk/java/funds/index.md
The Java SDK from Market Data provides methods to streamline your use of the Funds endpoints. These methods provide a typed interface over the underlying HTTP requests and responses, with both sync and async variants.
Reach the resource through `client.funds()`. For CSV output use `client.funds().asCsv()`.
## Funds Endpoints
---
# Fund Candles (Java SDK)
Source: https://www.marketdata.app/docs/sdk/java/funds/candles/index.md
Retrieve a mutual fund's NAV (net asset value) OHLC series.
## Making Requests
Use the `candles()` method on the `funds` resource, built with `FundCandlesRequest`. Funds report NAV, not traded volume, so there is **no volume column**, and only daily-and-coarser resolutions are available (no intraday).
```java
FundCandlesResponse candles(FundCandlesRequest request)
CompletableFuture candlesAsync(FundCandlesRequest request)
```
### FundCandlesRequest
```java
FundCandlesRequest.of(FundResolution resolution, String symbol)
FundCandlesRequest.builder(FundResolution resolution, String symbol)
.date(LocalDate date) // a single day
.from(LocalDate from) // window start (inclusive)
.to(LocalDate to) // window end (exclusive)
.countback(int n) // N candles before `to`
.build()
```
### FundResolution
A value type for the candle interval — daily and coarser only:
```java
FundResolution.DAILY // also WEEKLY, MONTHLY, YEARLY
FundResolution.days(1)
FundResolution.weeks(1)
FundResolution.of("D") // any raw wire token
```
#### Returns
`FundCandlesResponse` wrapping `List`:
```java
public record FundCandle(
@Nullable ZonedDateTime time, // bar opening moment (America/New_York)
@Nullable Double open, // NAV at open
@Nullable Double high,
@Nullable Double low,
@Nullable Double close) // NAV at close — note: no volume
```
## Examples
### Java
```java
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.funds.FundCandle;
import com.marketdata.sdk.funds.FundCandlesRequest;
import com.marketdata.sdk.funds.FundResolution;
import java.time.LocalDate;
try (MarketDataClient client = new MarketDataClient()) {
var candles = client.funds().candles(
FundCandlesRequest.builder(FundResolution.DAILY, "VFINX")
.from(LocalDate.now().minusWeeks(2))
.to(LocalDate.now())
.build());
for (FundCandle bar : candles.values()) {
System.out.printf("%s O=%.2f H=%.2f L=%.2f C=%.2f%n",
bar.time(), bar.open(), bar.high(), bar.low(), bar.close());
}
}
```
### Kotlin
```kotlin
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.funds.FundCandlesRequest
import com.marketdata.sdk.funds.FundResolution
import java.time.LocalDate
MarketDataClient().use { client ->
val candles = client.funds().candles(
FundCandlesRequest.builder(FundResolution.DAILY, "VFINX")
.from(LocalDate.now().minusWeeks(2))
.to(LocalDate.now())
.build())
for (bar in candles.values()) {
println("${bar.time()} close=${bar.close()}")
}
}
```
---
# Installation (Java SDK)
Source: https://www.marketdata.app/docs/sdk/java/installation/index.md
This guide will help you install the Market Data Java SDK and configure it for your project.
## Prerequisites
- **JDK 17 or newer.** The SDK is compiled for Java 17 and is tested on JDK 17, 21, and 25.
- A build tool: [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/).
## Basic Installation
Add the SDK as a dependency. The published artifact is a single JAR; it does **not** pull in a Kotlin standard library or a third-party HTTP client.
### Gradle (Kotlin DSL)
```kotlin
// build.gradle.kts
dependencies {
implementation("app.marketdata:marketdata-sdk-java:1.0.0")
}
```
### Gradle (Groovy DSL)
```groovy
// build.gradle
dependencies {
implementation 'app.marketdata:marketdata-sdk-java:1.0.0'
}
```
### Maven
```xml
app.marketdatamarketdata-sdk-java1.0.0
```
> [!TIP]
> Always check the [latest release](https://github.com/MarketDataApp/sdk-java/releases) for the current version number.
> [!NOTE]
> The Maven **groupId** is `app.marketdata` (the published Maven Central namespace), while the Java **package** you import from is `com.marketdata.sdk` — for example `import com.marketdata.sdk.MarketDataClient;`. The two are intentionally different.
## Kotlin Support
Although the SDK is written in Java, Kotlin consumers are a first-class audience:
- The public API is null-annotated, so Kotlin sees real nullable/non-null types — not platform types (`String!`).
- `MarketDataClient` is `AutoCloseable`, so you can use `client.use { ... }`.
- Async methods return `java.util.concurrent.CompletableFuture`. If you use coroutines, bridge it with `kotlinx-coroutines-jdk8`'s `await()` — the SDK does **not** depend on coroutines itself.
## Requirements & Dependencies
The SDK keeps its dependency footprint small:
- **No third-party HTTP client.** It uses the JDK's built-in `java.net.http.HttpClient` (HTTP/2).
- **Jackson** (`jackson-databind`) for JSON decoding.
- **JSpecify** nullability annotations — compile-time only, with no runtime cost.
## Next Steps
After installation, you'll need to:
1. Set up your [authentication token](https://www.marketdata.app/docs/sdk/java/authentication).
2. Learn about the [client](https://www.marketdata.app/docs/sdk/java/client) and how to make your first API requests.
3. Configure [Settings](https://www.marketdata.app/docs/sdk/java/settings) to customize output format, date format, and other universal parameters.
---
# Markets (Java SDK)
Source: https://www.marketdata.app/docs/sdk/java/markets/index.md
The Java SDK from Market Data provides methods to streamline your use of the Markets endpoints. These methods provide a typed interface over the underlying HTTP requests and responses, with both sync and async variants.
Reach the resource through `client.markets()`. For CSV output use `client.markets().asCsv()`.
## Markets Endpoints
---
# Market Status (Java SDK)
Source: https://www.marketdata.app/docs/sdk/java/markets/status/index.md
Retrieve the exchange open/closed calendar — whether the market was (or will be) open on a given day.
> [!NOTE]
> This is distinct from [`utilities().status()`](https://www.marketdata.app/docs/sdk/java/utilities/status), which reports the Market Data **API's** own service health.
## Making Requests
Use the `status()` method on the `markets` resource, built with `MarketStatusRequest`. Every parameter is optional; a bare request returns today's status for the US market.
```java
MarketStatusResponse status(MarketStatusRequest request)
CompletableFuture statusAsync(MarketStatusRequest request)
```
### MarketStatusRequest
```java
MarketStatusRequest.of() // today, US
MarketStatusRequest.builder()
.country(String country) // ISO 3166, 2-letter (default: US)
.date(LocalDate date) // a single day
.from(LocalDate from) // range start (inclusive)
.to(LocalDate to) // range end (inclusive)
.countback(int n) // N days before `to`
.build()
```
#### Returns
`MarketStatusResponse` wrapping `List`:
```java
public record MarketStatus(
@Nullable ZonedDateTime date,
@Nullable String status) // "open" / "closed" / null (outside coverage)
// also: boolean isOpen()
```
## Examples
### Java
```java
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.markets.MarketStatus;
import com.marketdata.sdk.markets.MarketStatusRequest;
import java.time.LocalDate;
try (MarketDataClient client = new MarketDataClient()) {
var status = client.markets().status(
MarketStatusRequest.builder()
.from(LocalDate.now().minusDays(7))
.to(LocalDate.now())
.build());
for (MarketStatus day : status.values()) {
System.out.println(day.date().toLocalDate() + " " + day.status()
+ (day.isOpen() ? " (trading)" : ""));
}
}
```
### Kotlin
```kotlin
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.markets.MarketStatusRequest
import java.time.LocalDate
MarketDataClient().use { client ->
val status = client.markets().status(
MarketStatusRequest.builder()
.from(LocalDate.now().minusDays(7))
.to(LocalDate.now())
.build())
for (day in status.values()) {
println("${day.date().toLocalDate()} ${day.status()}")
}
}
```
---
# Options (Java SDK)
Source: https://www.marketdata.app/docs/sdk/java/options/index.md
The Java SDK from Market Data provides methods to streamline your use of the Options endpoints. These methods provide a typed interface over the underlying HTTP requests and responses, with both sync and async variants.
Reach the resource through `client.options()`. For CSV output use `client.options().asCsv()`.
## Options Endpoints
---
# Option Chain (Java SDK)
Source: https://www.marketdata.app/docs/sdk/java/options/chain/index.md
Retrieve a full option chain for an underlying, with a rich set of filters. The chain is where most option workflows start.
## Making Requests
Use the `chain()` method on the `options` resource, built with `OptionsChainRequest`.
```java
OptionsChainResponse chain(OptionsChainRequest request)
CompletableFuture chainAsync(OptionsChainRequest request)
```
### OptionsChainRequest
The chain has two mutually-exclusive filter axes modeled as **sealed types**, so the compiler lets you pick exactly one variant of each:
```java
OptionsChainRequest.builder(String symbol)
// Expiration selection (sealed ExpirationFilter — pick one):
.expirationFilter(ExpirationFilter.onDate(LocalDate date))
.expirationFilter(ExpirationFilter.dte(int days)) // days-to-expiration
.expirationFilter(ExpirationFilter.between(LocalDate from, LocalDate to))
.expirationFilter(ExpirationFilter.all()) // every expiration
// (if omitted, the API narrows to the front month)
// Strike selection (sealed StrikeFilter — pick one):
.strikeFilter(StrikeFilter.exact(double price))
.strikeFilter(StrikeFilter.range(double min, double max))
// Additive filters:
.side(OptionSide.CALL) // CALL or PUT
.strikeRange(StrikeRange.ITM) // ITM / OTM / ATM
.strikeLimit(int limit) // max strikes per side
.delta(double delta)
.minOpenInterest(long oi)
.minVolume(long vol)
.date(LocalDate date) // historical chain
.build()
```
#### Returns
`OptionsChainResponse` wrapping `List`:
```java
public record OptionQuote(
@Nullable String optionSymbol, @Nullable String underlying,
@Nullable ZonedDateTime expiration, @Nullable String side, @Nullable Double strike,
@Nullable ZonedDateTime firstTraded, @Nullable Integer dte, @Nullable ZonedDateTime updated,
@Nullable Double bid, @Nullable Long bidSize,
@Nullable Double mid, @Nullable Double ask, @Nullable Long askSize,
@Nullable Double last, @Nullable Long openInterest, @Nullable Long volume,
@Nullable Boolean inTheMoney, @Nullable Double intrinsicValue, @Nullable Double extrinsicValue,
@Nullable Double underlyingPrice, @Nullable Double iv,
// greeks:
@Nullable Double delta, @Nullable Double gamma, @Nullable Double theta,
@Nullable Double vega, @Nullable Double rho)
```
## Examples
### Java
```java
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.options.ExpirationFilter;
import com.marketdata.sdk.options.OptionQuote;
import com.marketdata.sdk.options.OptionSide;
import com.marketdata.sdk.options.OptionsChainRequest;
import com.marketdata.sdk.options.StrikeFilter;
import java.util.List;
try (MarketDataClient client = new MarketDataClient()) {
// The 5 calls nearest the money.
List chain = client.options().chain(
OptionsChainRequest.builder("AAPL")
.side(OptionSide.CALL)
.strikeLimit(5)
.build())
.values();
for (OptionQuote c : chain) {
System.out.println(c.optionSymbol() + " strike=" + c.strike()
+ " bid/ask=" + c.bid() + "/" + c.ask() + " delta=" + c.delta());
}
// Sealed filters: contracts within 45 days, strikes between 150 and 250.
var filtered = client.options().chain(
OptionsChainRequest.builder("AAPL")
.expirationFilter(ExpirationFilter.dte(45))
.strikeFilter(StrikeFilter.range(150, 250))
.side(OptionSide.CALL)
.build());
}
```
### Kotlin
```kotlin
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.options.OptionSide
import com.marketdata.sdk.options.OptionsChainRequest
MarketDataClient().use { client ->
val chain = client.options().chain(
OptionsChainRequest.builder("AAPL")
.side(OptionSide.CALL)
.strikeLimit(5)
.build())
.values()
for (c in chain) {
println("${c.optionSymbol()} strike=${c.strike()} delta=${c.delta()}")
}
}
```
---
# Expirations (Java SDK)
Source: https://www.marketdata.app/docs/sdk/java/options/expirations/index.md
List the available expiration dates for an underlying.
## Making Requests
Use the `expirations()` method on the `options` resource, built with `OptionsExpirationsRequest`.
```java
OptionsExpirationsResponse expirations(OptionsExpirationsRequest request)
CompletableFuture expirationsAsync(OptionsExpirationsRequest request)
```
### OptionsExpirationsRequest
```java
OptionsExpirationsRequest.of(String symbol)
OptionsExpirationsRequest.builder(String symbol)
.strike(double strike) // only expirations that list this strike
.date(LocalDate date) // historical: the calendar as it stood on this date
.build()
```
#### Returns
`OptionsExpirationsResponse` wrapping `List` — the expiration dates.
## Examples
### Java
```java
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.options.OptionsExpirationsRequest;
import java.time.ZonedDateTime;
try (MarketDataClient client = new MarketDataClient()) {
var expirations = client.options().expirations(OptionsExpirationsRequest.of("AAPL"));
System.out.println("AAPL has " + expirations.values().size() + " expirations");
for (ZonedDateTime exp : expirations.values()) {
System.out.println(" " + exp.toLocalDate());
}
}
```
### Kotlin
```kotlin
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.options.OptionsExpirationsRequest
MarketDataClient().use { client ->
val expirations = client.options().expirations(OptionsExpirationsRequest.of("AAPL"))
println("AAPL has ${expirations.values().size} expirations")
}
```
---
# Lookup (Java SDK)
Source: https://www.marketdata.app/docs/sdk/java/options/lookup/index.md
Turn a human-readable option description into a well-formed OCC option symbol.
## Making Requests
Use the `lookup()` method on the `options` resource, built with `OptionsLookupRequest`.
```java
OptionsLookupResponse lookup(OptionsLookupRequest request)
CompletableFuture lookupAsync(OptionsLookupRequest request)
```
### OptionsLookupRequest
```java
OptionsLookupRequest.of(String userInput)
```
#### Returns
`OptionsLookupResponse` wrapping a `String` — the OCC option symbol. Read it with `.values()`.
## Examples
### Java
```java
import com.marketdata.sdk.MarketDataClient;
import com.marketdata.sdk.options.OptionsLookupRequest;
try (MarketDataClient client = new MarketDataClient()) {
String occSymbol = client.options()
.lookup(OptionsLookupRequest.of("AAPL 1/16/2026 $200 Call"))
.values();
System.out.println("Resolved to: " + occSymbol); // AAPL271217C00300000
}
```
### Kotlin
```kotlin
import com.marketdata.sdk.MarketDataClient
import com.marketdata.sdk.options.OptionsLookupRequest
MarketDataClient().use { client ->
val occSymbol = client.options()
.lookup(OptionsLookupRequest.of("AAPL 1/16/2026 \$200 Call"))
.values()
println("Resolved to: $occSymbol")
}
```
---
# Option Quotes (Java SDK)
Source: https://www.marketdata.app/docs/sdk/java/options/quotes/index.md
Retrieve quotes (bid, ask, last, greeks, etc.) for one or more option contracts by OCC symbol.
## Making Requests
The `options` resource offers two quote methods:
- `quote(...)` — a single contract, built with `OptionsQuoteRequest`.
- `quotes(...)` — several contracts. Unlike stocks, options quotes **fan out one request per symbol concurrently**, so the result is a `Map` keyed by OCC symbol (insertion order preserved).
```java
OptionsQuotesResponse quote(OptionsQuoteRequest request)
CompletableFuture quoteAsync(OptionsQuoteRequest request)
Map quotes(OptionsQuotesRequest request)
CompletableFuture