Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ jobs:
client-payload: >-
{
"source": "docs",
"source_repo": "MarketDataApp/documentation",
"environment": "${{ steps.env.outputs.environment }}",
"commit_sha": "${{ github.sha }}"
}
Expand Down
2 changes: 1 addition & 1 deletion account/cancellations.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Cancellations
sidebar_position: 8
sidebar_position: 9
---

Your Market Data account can be cancelled at any time.
Expand Down
2 changes: 1 addition & 1 deletion account/data-policies/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Data Policies
sidebar_position: 9
sidebar_position: 10
---

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.
Expand Down
2 changes: 1 addition & 1 deletion account/feature-requests.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Feature Requests
sidebar_position: 10
sidebar_position: 11
---

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.
Expand Down
31 changes: 31 additions & 0 deletions account/invoices.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
title: Invoices
sidebar_position: 7
---

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](/docs/account/billing-portal#how-to-access-the-billing-portal-for-the-first-time) for instructions on setting up your password.
2 changes: 1 addition & 1 deletion account/plan-limits.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,4 @@ The Free Forever plan only provides access to pricing data. Premium endpoints an

- 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.
- All limits are hard limits. Once reached, requests are blocked until the relevant window resets.
- 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.
2 changes: 1 addition & 1 deletion account/review-us.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Review Us
sidebar_position: 11
sidebar_position: 12
---

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.
Expand Down
2 changes: 1 addition & 1 deletion account/upgrades.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Plan Upgrades
sidebar_position: 7
sidebar_position: 8
---

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:
Expand Down
6 changes: 6 additions & 0 deletions api/dates-and-times.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ title: Dates and Times
sidebar_position: 5
---

## 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`](/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.

## 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.
Expand Down
6 changes: 3 additions & 3 deletions api/funds/candles.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ GET
<Tabs>
<TabItem value="HTTP" label="HTTP" default>

**GET** [https://api.marketdata.app/v1/funds/candles/D/VFINX?from=2020-01-01&to=2020-01-10](https://api.marketdata.app/v1/funds/candles/D/VFINX?from=2020-01-01&to=2020-01-10)
**GET** [https://api.marketdata.app/v1/funds/candles/D/VFINX/?from=2020-01-01&to=2020-01-10](https://api.marketdata.app/v1/funds/candles/D/VFINX/?from=2020-01-01&to=2020-01-10)

</TabItem>
<TabItem value="NodeJS" label="NodeJS">

```js title="fundCandles.js"
fetch(
"https://api.marketdata.app/v1/funds/candles/D/VFINX?from=2020-01-01&to=2020-01-10"
"https://api.marketdata.app/v1/funds/candles/D/VFINX/?from=2020-01-01&to=2020-01-10"
)
.then((res) => {
console.log(res);
Expand Down Expand Up @@ -172,7 +172,7 @@ echo $candles;

- **t** `array[number]`

Candle time (Unix timestamp, Eastern Time Zone). Daily, weekly, monthly, yearly candles are returned without times.
Candle time (Unix timestamp). This is midnight US Eastern Time on the session date (e.g., 05:00 UTC during EST or 04:00 UTC during EDT).

</TabItem>
<TabItem value="NoData" label="No Data">
Expand Down
4 changes: 2 additions & 2 deletions api/options/expirations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ GET
<Tabs>
<TabItem value="HTTP" label="HTTP" default>

**GET** [https://api.marketdata.app/v1/options/expirations/AAPL](https://api.marketdata.app/v1/options/expirations/AAPL)
**GET** [https://api.marketdata.app/v1/options/expirations/AAPL/](https://api.marketdata.app/v1/options/expirations/AAPL/)

</TabItem>
<TabItem value="NodeJS" label="NodeJS">

```js title="app.js"
fetch("https://api.marketdata.app/v1/options/expirations/AAPL")
fetch("https://api.marketdata.app/v1/options/expirations/AAPL/")
.then((res) => {
console.log(res);
})
Expand Down
6 changes: 3 additions & 3 deletions api/options/lookup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import TabItem from "@theme/TabItem";

## Endpoint
```
https://api.marketdata.app/v1/options/lookup/{userInput}
https://api.marketdata.app/v1/options/lookup/{userInput}/
```
#### Method
```
Expand All @@ -21,14 +21,14 @@ GET
<Tabs>
<TabItem value="HTTP" label="HTTP" default>

**GET** [https://api.marketdata.app/v1/options/lookup/AAPL%207/28/2023%20200%20Call](https://api.marketdata.app/v1/options/lookup/AAPL%207/28/2023%20200%20Call)
**GET** [https://api.marketdata.app/v1/options/lookup/AAPL%207/28/2023%20200%20Call/](https://api.marketdata.app/v1/options/lookup/AAPL%207/28/2023%20200%20Call/)

</TabItem>
<TabItem value="NodeJS" label="NodeJS">

```js title="app.js"
fetch(
"https://api.marketdata.app/v1/options/lookup/AAPL%207/28/2023%20200%20Call"
"https://api.marketdata.app/v1/options/lookup/AAPL%207/28/2023%20200%20Call/"
)
.then((res) => {
console.log(res);
Expand Down
2 changes: 1 addition & 1 deletion api/rate-limiting.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ For troubleshooting, use:

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).

**The credit limit is a hard limit. Once the limit has been reached, you can no longer make API calls until the credit counter resets.** Calls in excess of the limit return `429` responses.
**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

Expand Down
2 changes: 1 addition & 1 deletion api/stocks/bulkcandles.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ echo $candles;

- **t** `array[number]`

Candle time (Unix timestamp, Exchange Timezone). Daily candles are returned at 00:00:00 without times.
Candle time (Unix timestamp). This is midnight US Eastern Time on the session date (e.g., 05:00 UTC during EST or 04:00 UTC during EDT).

</TabItem>
<TabItem value="NoData" label="No Data">
Expand Down
11 changes: 8 additions & 3 deletions api/stocks/candles.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ GET
<Tabs>
<TabItem value="HTTP" label="HTTP" default>

**GET** [https://api.marketdata.app/v1/stocks/candles/D/AAPL?from=2020-01-01&to=2020-12-31](https://api.marketdata.app/v1/stocks/candles/D/AAPL?from=2020-01-01&to=2020-12-31)
**GET** [https://api.marketdata.app/v1/stocks/candles/D/AAPL/?from=2020-01-01&to=2020-12-31](https://api.marketdata.app/v1/stocks/candles/D/AAPL/?from=2020-01-01&to=2020-12-31)

</TabItem>
<TabItem value="NodeJS" label="NodeJS">

```js title="app.js"
fetch(
"https://api.marketdata.app/v1/stocks/candles/D/AAPL?from=2020-01-01&to=2020-12-31"
"https://api.marketdata.app/v1/stocks/candles/D/AAPL/?from=2020-01-01&to=2020-12-31"
)
.then((res) => {
console.log(res);
Expand Down Expand Up @@ -201,7 +201,12 @@ There is no maximum date range limit on daily candles. When requesting intraday
Volume.

- **t** `array[number]`
Candle time (Unix timestamp, UTC). Daily, weekly, monthly, yearly candles are returned without times.

Candle time (Unix timestamp). For intraday candles, this is the candle's start time in UTC. For daily and longer resolutions, this is midnight US Eastern Time on the session date (e.g., 05:00 UTC during EST or 04:00 UTC during EDT).

:::tip Understanding Daily Candle Timestamps
A daily candle for **Friday, March 6, 2026** has Unix timestamp `1772773200`, which equals **05:00 UTC on March 6** (= 00:00 Eastern). The 05:00 UTC time does not represent a market event — it is simply midnight Eastern expressed in UTC. The date portion (March 6) identifies the trading session.
:::

</TabItem>
<TabItem value="NoData" label="No Data">
Expand Down
2 changes: 1 addition & 1 deletion api/troubleshooting/logging.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const fs = require("fs");

// Make the API request
axios
.get("https://api.marketdata.app/v1/your_endpoint_here")
.get("https://api.marketdata.app/v1/your_endpoint_here/")
.then((response) => {
// Do nothing for successful responses
})
Expand Down
4 changes: 0 additions & 4 deletions src/theme/NavbarItem/UserProfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ export default function UserProfile({ mobile }) {
initUserProfile({
container: ref.current,
dropdown: true,
loginUrl: 'https://www.marketdata.app/dashboard/',
logoutUrl: 'https://dashboard.marketdata.app/marketdata/logout',
dashboardUrl: 'https://www.marketdata.app/dashboard/',
loginText: 'Log in',
}).then((fn) => {
if (cancelled) {
fn();
Expand Down
37 changes: 37 additions & 0 deletions worker/handler.integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,41 @@ for (const [env, { host }] of Object.entries(envs)) {
});
}
});

describe(`canonical Link header — ${env} (live sitemap)`, async () => {
const paths = (await fetchSitemapUrls()).filter((p) => !shouldSkip(p));

it(`sitemap has URLs to test`, () => {
expect(paths.length).toBeGreaterThan(50);
});

for (const path of paths) {
it(`${path} canonical URL returns 200`, async () => {
const url = `${host}${path}`;
const mdRes = await fetch(url, {
headers: { Accept: 'text/markdown' },
redirect: 'follow',
});

// Skip if markdown serving itself failed
if (mdRes.status !== 200) return;

const linkHeader = mdRes.headers.get('link');
expect(linkHeader, `${path} has no Link header`).toBeTruthy();

// Extract canonical URL from Link header: <URL>; rel="canonical"
const match = linkHeader.match(/^<([^>]+)>;\s*rel="canonical"$/);
expect(match, `${path} Link header malformed: ${linkHeader}`).toBeTruthy();

const canonicalUrl = match[1];

// Fetch the canonical URL without following redirects
const canonicalRes = await fetch(canonicalUrl, { redirect: 'manual' });
expect(
canonicalRes.status,
`${path} canonical ${canonicalUrl} returned ${canonicalRes.status} (expected 200)`
).toBe(200);
});
}
});
}
15 changes: 9 additions & 6 deletions worker/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ async function handleRequest(request) {
return fetch(request);
}

// Redirect misrouted cdn-cgi paths (e.g. Zaraz relative URL requests from deep doc pages)
// e.g. /docs/api/stocks/prices/cdn-cgi/zaraz/i.js → /cdn-cgi/zaraz/i.js
const cdnCgiIndex = url.pathname.indexOf('/cdn-cgi/');
if (cdnCgiIndex !== -1) {
const cdnCgiPath = url.pathname.slice(cdnCgiIndex);
return Response.redirect(`https://${url.hostname}${cdnCgiPath}`, 302);
}

// Redirect legacy SDK PHP docs to GitHub Pages
const sdkPhpPrefix = '/docs/sdk-php/';
if (url.pathname.startsWith(sdkPhpPrefix) || url.pathname === '/docs/sdk-php') {
Expand All @@ -96,7 +104,7 @@ async function handleRequest(request) {
if (url.pathname.endsWith(indexHtmlMd)) {
stem = url.pathname.slice(docsPrefix.length, -indexHtmlMd.length);
} else {
stem = url.pathname.slice(docsPrefix.length, -3);
stem = url.pathname.slice(docsPrefix.length, -3).replace(/\/index$/, '');
}
} else {
stem = url.pathname.replace(/\/$/, '').slice(docsPrefix.length);
Expand Down Expand Up @@ -124,11 +132,6 @@ async function handleRequest(request) {
}
}

// Docs sites don't serve robots.txt; block stale cached copies
if (url.pathname.endsWith('/robots.txt')) {
return new Response('', { status: 404 });
}

url.hostname = target;
const response = await fetch(new Request(url, request), { cf: { cacheEverything: true } });

Expand Down
51 changes: 34 additions & 17 deletions worker/handler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,31 @@ describe('handleRequest', () => {
});
});

// --- cdn-cgi redirect ---

describe('cdn-cgi redirect', () => {
it('redirects misrouted cdn-cgi paths embedded in docs paths', async () => {
const req = makeRequest('https://www.marketdata.app/docs/api/stocks/prices/cdn-cgi/zaraz/i.js');
const res = await handleRequest(req);
expect(res.status).toBe(302);
expect(res.headers.get('location')).toBe('https://www.marketdata.app/cdn-cgi/zaraz/i.js');
});

it('redirects cdn-cgi at top-level docs path', async () => {
const req = makeRequest('https://www.marketdata.app/docs/cdn-cgi/zaraz/i.js');
const res = await handleRequest(req);
expect(res.status).toBe(302);
expect(res.headers.get('location')).toBe('https://www.marketdata.app/cdn-cgi/zaraz/i.js');
});

it('redirects misrouted cdn-cgi on staging hostname', async () => {
const req = makeRequest('https://www-staging.marketdata.app/docs/api/stocks/cdn-cgi/zaraz/i.js');
const res = await handleRequest(req);
expect(res.status).toBe(302);
expect(res.headers.get('location')).toBe('https://www-staging.marketdata.app/cdn-cgi/zaraz/i.js');
});
});

// --- SDK PHP redirects ---

describe('SDK PHP redirect', () => {
Expand Down Expand Up @@ -202,6 +227,15 @@ describe('handleRequest', () => {
);
});

it('canonical URL strips /index from index.md requests', async () => {
mockFetch.mockResolvedValueOnce(new Response('# Plans\n', { status: 200 }));
const req = makeRequest('https://www.marketdata.app/docs/account/plans/index.md');
const res = await handleRequest(req);
expect(res.headers.get('link')).toBe(
'<https://www.marketdata.app/docs/account/plans/>; rel="canonical"'
);
});

it('handles nested path with index.html.md', async () => {
mockFetch.mockResolvedValueOnce(new Response('# Candles\n', { status: 200 }));
const req = makeRequest('https://www.marketdata.app/docs/api/stocks/candles/index.html.md');
Expand Down Expand Up @@ -273,23 +307,6 @@ describe('handleRequest', () => {
});
});

// --- robots.txt ---

describe('robots.txt blocking', () => {
it('returns 404 for /docs/robots.txt on production', async () => {
const req = makeRequest('https://www.marketdata.app/docs/robots.txt');
const res = await handleRequest(req);
expect(res.status).toBe(404);
expect(mockFetch).not.toHaveBeenCalled();
});

it('returns 404 for /docs/robots.txt on staging', async () => {
const req = makeRequest('https://www-staging.marketdata.app/docs/robots.txt');
const res = await handleRequest(req);
expect(res.status).toBe(404);
expect(mockFetch).not.toHaveBeenCalled();
});
});

// --- Route proxying ---

Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1725,8 +1725,8 @@
integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==

"@marketdataapp/ui@github:MarketDataApp/ui#main":
version "2.10.1"
resolved "https://codeload.github.com/MarketDataApp/ui/tar.gz/328533f0d7a2afb776b45ea08b17a4b2445d6199"
version "4.0.0"
resolved "https://codeload.github.com/MarketDataApp/ui/tar.gz/a6c2eadcd554f026ff9dd02f7ebf968f5f7424da"

"@mdx-js/mdx@^3.0.0":
version "3.1.1"
Expand Down
Loading