diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 5d8d2a48..e1f0839e 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -87,6 +87,7 @@ jobs: client-payload: >- { "source": "docs", + "source_repo": "MarketDataApp/documentation", "environment": "${{ steps.env.outputs.environment }}", "commit_sha": "${{ github.sha }}" } diff --git a/account/cancellations.md b/account/cancellations.md index 40191e9b..44da24ac 100644 --- a/account/cancellations.md +++ b/account/cancellations.md @@ -1,6 +1,6 @@ --- title: Cancellations -sidebar_position: 8 +sidebar_position: 9 --- Your Market Data account can be cancelled at any time. diff --git a/account/data-policies/index.md b/account/data-policies/index.md index 38898a9b..86a0d28c 100644 --- a/account/data-policies/index.md +++ b/account/data-policies/index.md @@ -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. diff --git a/account/feature-requests.md b/account/feature-requests.md index 5f422301..67b6428d 100644 --- a/account/feature-requests.md +++ b/account/feature-requests.md @@ -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. diff --git a/account/invoices.md b/account/invoices.md new file mode 100644 index 00000000..7c40bb5b --- /dev/null +++ b/account/invoices.md @@ -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. diff --git a/account/plan-limits.md b/account/plan-limits.md index 68778a8b..aaf4d0f4 100644 --- a/account/plan-limits.md +++ b/account/plan-limits.md @@ -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. diff --git a/account/review-us.md b/account/review-us.md index 439537f9..a684d5c4 100644 --- a/account/review-us.md +++ b/account/review-us.md @@ -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. diff --git a/account/upgrades.md b/account/upgrades.md index 7114398a..4d399880 100644 --- a/account/upgrades.md +++ b/account/upgrades.md @@ -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: diff --git a/api/dates-and-times.mdx b/api/dates-and-times.mdx index 04843551..ae13c510 100644 --- a/api/dates-and-times.mdx +++ b/api/dates-and-times.mdx @@ -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. diff --git a/api/funds/candles.mdx b/api/funds/candles.mdx index 78fd2526..ac52358a 100644 --- a/api/funds/candles.mdx +++ b/api/funds/candles.mdx @@ -27,14 +27,14 @@ GET -**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) ```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); @@ -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). diff --git a/api/options/expirations.mdx b/api/options/expirations.mdx index 7ebc7b05..b6473b46 100644 --- a/api/options/expirations.mdx +++ b/api/options/expirations.mdx @@ -21,13 +21,13 @@ GET -**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/) ```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); }) diff --git a/api/options/lookup.mdx b/api/options/lookup.mdx index 2cf00ae3..57d8213a 100644 --- a/api/options/lookup.mdx +++ b/api/options/lookup.mdx @@ -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 ``` @@ -21,14 +21,14 @@ GET -**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/) ```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); diff --git a/api/rate-limiting.md b/api/rate-limiting.md index c837847b..d9a0d0d3 100644 --- a/api/rate-limiting.md +++ b/api/rate-limiting.md @@ -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 diff --git a/api/stocks/bulkcandles.mdx b/api/stocks/bulkcandles.mdx index 6f94dc32..36e61d33 100644 --- a/api/stocks/bulkcandles.mdx +++ b/api/stocks/bulkcandles.mdx @@ -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). diff --git a/api/stocks/candles.mdx b/api/stocks/candles.mdx index e69bb321..662f22eb 100644 --- a/api/stocks/candles.mdx +++ b/api/stocks/candles.mdx @@ -21,14 +21,14 @@ GET -**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) ```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); @@ -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. + ::: diff --git a/api/troubleshooting/logging.mdx b/api/troubleshooting/logging.mdx index d9c61c0c..5180a26e 100644 --- a/api/troubleshooting/logging.mdx +++ b/api/troubleshooting/logging.mdx @@ -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 }) diff --git a/src/theme/NavbarItem/UserProfile.js b/src/theme/NavbarItem/UserProfile.js index 2f003c27..43b8862a 100644 --- a/src/theme/NavbarItem/UserProfile.js +++ b/src/theme/NavbarItem/UserProfile.js @@ -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(); diff --git a/worker/handler.integration.test.js b/worker/handler.integration.test.js index 4d69d8f9..462c3626 100644 --- a/worker/handler.integration.test.js +++ b/worker/handler.integration.test.js @@ -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: ; 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); + }); + } + }); } diff --git a/worker/handler.js b/worker/handler.js index f88d5e7f..78dad7a5 100644 --- a/worker/handler.js +++ b/worker/handler.js @@ -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') { @@ -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); @@ -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 } }); diff --git a/worker/handler.test.js b/worker/handler.test.js index 4a4942c3..ce8d0a7f 100644 --- a/worker/handler.test.js +++ b/worker/handler.test.js @@ -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', () => { @@ -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( + '; 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'); @@ -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 --- diff --git a/yarn.lock b/yarn.lock index 7efd971a..88434f26 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"