docs(developer-hub): replace Proxy page with Frontend Auth guide#3912
Conversation
Adds `content/docs/price-feeds/pro/frontend-auth.mdx` as a first-class how-to for the short-lived-JWT flow that browser apps should use in place of embedding the long-lived Pro API key. The page documents `POST /auth/token` against the current OpenAPI spec, gives Node/Express backend and browser TypeScript examples, and covers protecting the customer-hosted token endpoint. Slots into `price-feeds/pro/meta.json` right after `acquire-api-key`. Deletes the beta `api/proxy.mdx` page (the hosted proxy story is going away) and removes its entries from the API-reference nav and services table. Retargets every remaining pointer to `history#jwt-authentication` (rest.mdx, faq.mdx, history.mdx's warning callout) at the new page, and adds a bullet to `acquire-api-key.mdx`'s Next Steps so frontend readers land on the right guide.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
5 Skipped Deployments
|
🤖 llms.txt Content ReviewAnalyzed documentation changes — no updates to curated content required. Products reviewed:
Files analyzed |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8c803bbddf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ); | ||
| ``` | ||
|
|
||
| The same JWT authenticates the REST API on `https://pyth-lazer.dourolabs.app` and the WebSocket `subscribe` message — anywhere the docs say `Authorization: Bearer <PRO_API_KEY>`, a browser client can pass the JWT instead. |
There was a problem hiding this comment.
Avoid documenting JWTs as WebSocket subscribe auth
For browser clients this gives an unusable WebSocket path: the existing WebSocket docs and generated examples authenticate the WebSocket handshake with an Authorization header, while the subscribe message schema only contains subscription fields. Native browser WebSocket calls cannot add that header, so a frontend following this guide can't directly use the minted JWT for WebSocket streams unless the SDK/API supports another transport-specific auth mechanism; please either document that mechanism or keep WebSocket behind the backend.
Useful? React with 👍 / 👎.
| --- | ||
| title: Proxy API (Beta) | ||
| description: Simplified access to Pyth Pro prices without authentication | ||
| slug: /price-feeds/pro/api/proxy |
There was a problem hiding this comment.
Redirect the removed Proxy API URL
This deletes the only page that owns the public slug /price-feeds/pro/api/proxy, but there is no matching redirect in apps/developer-hub/next.config.js and a repo-wide search for the slug only finds this removal. Users or search results still linking to the Proxy API page will hit a 404 even though the page is being replaced by /price-feeds/pro/frontend-auth; please add a redirect before removing the page.
Useful? React with 👍 / 👎.
…-auth prose The Frontend Authentication guide said a browser passes the minted JWT in the WebSocket `subscribe` message. That is wrong: the subscribe message carries no auth field, and a browser cannot set an `Authorization` header on a WebSocket connection. The server also accepts the token as an `ACCESS_TOKEN` query parameter (see apps/hermes/server/src/api/token.rs), which a browser can set, so document that mechanism instead. Also tighten the prose throughout: active voice, plainer words, and fewer figures of speech. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RENokHWDDRzELLNWb18yx2
…entication The Proxy (Beta) API page owned the slug /price-feeds/pro/api/proxy and was deleted in this PR. The general catch-all redirect excludes /price-feeds/pro/*, so without an explicit rule the old URL would 404. Add a permanent redirect to the Frontend Authentication guide that replaces it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RENokHWDDRzELLNWb18yx2
Sibling Pyth Pro pages open with a plain lead paragraph rather than an "## Overview" heading. Drop the heading and rewrite the opener to fit: say what the guide is for, then the short-lived-JWT mechanism. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RENokHWDDRzELLNWb18yx2
…orum guidance The Pyth Pro announcement documents the short-lived JWT only as a bearer token (Authorization: Bearer <JWT>) for the HTTP endpoints. It does not describe a WebSocket path or an ACCESS_TOKEN query parameter. Remove the unconfirmed WebSocket query-parameter mechanism, scope the JWT flow to the History and REST APIs, and recommend keeping browser WebSocket streaming behind the backend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RENokHWDDRzELLNWb18yx2
…lacement The frontend JWT flow works over the WebSocket and REST APIs (verified), so it replaces the removed beta Proxy service for frontend access. Surface that where a Proxy user would land: - frontend-auth.mdx: a callout framing the page as the Proxy replacement. - api/index.mdx: a note under the Services table (where the Proxy row used to be). - subscribe-to-prices.mdx: the browser WebSocket transport — the JWT as the ACCESS_TOKEN query parameter, since a browser can't set the Authorization header. - faq.mdx: a "What happened to the Proxy API?" entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RENokHWDDRzELLNWb18yx2
The browser example fetched a response into an unused variable. Read it so the snippet shows the full round-trip and trips no no-unused-locals lint if copied. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RENokHWDDRzELLNWb18yx2
|
|
||
| import { Callout } from "fumadocs-ui/components/callout"; | ||
|
|
||
| This guide explains how to call Pyth Pro from browser and frontend apps without shipping your `PRO_API_KEY` to users. Anything in a browser bundle is public, so your backend mints a short-lived JWT with `POST /auth/token` and hands it to the browser. The browser then presents that JWT in place of the API key — as an `Authorization: Bearer` header on the History and REST APIs, and as the `ACCESS_TOKEN` query parameter on a WebSocket connection. |
There was a problem hiding this comment.
We now accept the 'sec-websocket-protocol' header, which should be preferred over the ACCESS_TOKEN query param. I think we should treat ACCESS_TOKEN as deprecated and undocumented. For the sec-websocket-protocol approach, we can say something like:
The browser WebSocket API doesn't allow custom headers, so pass the token through the subprotocol list instead. Offer the marker pyth-lazer-auth immediately followed by your token:
const ws = new WebSocket(url, ["pyth-lazer-auth", "<YOUR_TOKEN>"]);
The server authenticates from that value and completes the handshake by echoing back the pyth-lazer-auth subprotocol. This is expected — your client should accept it and does not need to do anything with it.
| | Client | Pattern | | ||
| | --------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | ||
| | Server-to-server | Present the long-lived Pro API key directly as `Authorization: Bearer <PRO_API_KEY>`. Simplest — prefer this whenever the client can hold a secret. | | ||
| | Browser / frontend | Your backend mints a JWT via `POST /auth/token` and returns it to the browser, which presents it in place of the API key: `Authorization: Bearer <JWT>` on the History and REST APIs, or `?ACCESS_TOKEN=<JWT>` on a WebSocket. | |
There was a problem hiding this comment.
again, prefer pyth-lazer-auth via sec-websocket-protocol
|
|
||
| The same JWT works against the REST API on `https://pyth-lazer.dourolabs.app` — send it as `Authorization: Bearer <JWT>`, exactly as on the History API. | ||
|
|
||
| For the WebSocket API, a browser cannot set an `Authorization` header on the connection, so pass the JWT as the `ACCESS_TOKEN` query parameter instead: |
…ubprotocol Per reviewer guidance: the Lazer server accepts the token through the Sec-WebSocket-Protocol subprotocol, which is preferred over the ACCESS_TOKEN query param. Document the pyth-lazer-auth subprotocol as the browser WebSocket transport — offer the marker followed by the token, and note the server echoes it back to finish the handshake — and drop the ACCESS_TOKEN query param, now deprecated and undocumented. Updates the frontend-auth lead, pattern table, and WebSocket section, plus the subscribe-to-prices browser note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RENokHWDDRzELLNWb18yx2
Summary
Replaces the beta Proxy API page with a Frontend Authentication guide. It documents the short-lived-JWT flow that browser and frontend apps should use instead of embedding the long-lived Pro API key. The Pyth Pro History API starts requiring authentication on July 24, 2026, so this pattern needs to be in the docs before then.
Live testing confirmed the
/auth/tokenJWT is a Pyth Pro-wide credential: it authenticates the History, REST, and WebSocket APIs. So it also replaces the removed, unauthenticated Proxy service — a frontend streams over WebSocket, or queries REST and History, with a short-lived JWT instead of the proxy.How the JWT is presented
Authorization: Bearer <JWT>header.?ACCESS_TOKEN=<JWT>query parameter on the connection URL — a browser cannot set anAuthorizationheader on a WebSocket.PRO_API_KEYnever leaves the backend; it is used only to mint JWTs viaPOST /auth/token.Files
New page
content/docs/price-feeds/pro/frontend-auth.mdx— overview; "when to use which pattern" table; "replacing the Proxy" callout; the four-step mint flow;POST /auth/tokenreference (requestttl_seconds; 200 responseaccess_token/expires_at/token_type; 401/404 errors); Node/Express backend example (POST /pyth-token); browser TypeScript example (in-memory JWT cache with a refresh margin); the REST and WebSocket transports; "protecting the token endpoint" guidance; related links.Proxy removal + redirect
content/docs/price-feeds/pro/api/proxy.mdx— deleted.content/docs/price-feeds/pro/api/index.mdx— drop theProxy (Beta)row from the Services table; add a note pointing frontend apps to Frontend Authentication.content/docs/price-feeds/pro/api/meta.json— drop theProxy (Beta)nav entry.apps/developer-hub/next.config.js— permanent redirect/price-feeds/pro/api/proxy→/price-feeds/pro/frontend-auth.Retargeted links
content/docs/price-feeds/pro/api/history.mdx— replace the inlineJWT Authenticationsection with a pointer to the new page; top callout links to it.content/docs/price-feeds/pro/api/rest.mdx— top callout and the Authentication section point to the new page; drop the "run their own proxy" phrasing.content/docs/price-feeds/pro/acquire-api-key.mdx— the "browser apps must not embed the key" paragraph and Next Steps point to Frontend Authentication.content/docs/price-feeds/pro/faq.mdx— the "authenticate from a frontend app" answer points to the JWT flow; new "What happened to the Proxy API?" entry.Streaming transport
content/docs/price-feeds/pro/subscribe-to-prices.mdx— document the browser WebSocket transport: pass the JWT as theACCESS_TOKENquery parameter (a browser can't set theAuthorizationheader on a WebSocket).Nav
content/docs/price-feeds/pro/meta.json— addfrontend-authunder How-To Guides, afteracquire-api-key.Invariants
grep -rn 'price-feeds/pro/api/proxy' apps/developer-hub/content→ zero (the deleted slug survives only as the redirect source innext.config.js).grep -rn 'history#jwt-authentication' apps/developer-hub/content→ zero.PRO_API_KEYin a browser snippet — the remaining references are server-side (curl, backendprocess.env, or explicit "don't embed" warnings).POST /auth/tokenrequest/response fields match the Auth OpenAPI spec.Testing
POST /auth/token, then confirmed History, REST (POST /v1/latest_price), and WebSocket (/v1/stream) all accept it. A bogus token is rejected with 401; the WebSocket accepts the JWT via both theAuthorizationheader and theACCESS_TOKENquery parameter. This is the basis for documenting the JWT as Pyth Pro-wide.@pythnetwork/developer-hubrun in CI on each push.Reviewers
Please flag @aditya520 and @jayantk for content review before merge.