Sample audits · Open-source projects and documentation, eight more

Jupiter developer docs: pages changed or added since May

Only the Jupiter documentation pages that changed or were added since our May audit (112 changed, 77 added); unchanged pages were not audited again.

Auditeddevelopers.jup.ag documentation, changed and added pages, pages fetched 11 October 2026
Date11 October 2026
How it rancloud session, full audit, Standard review
Verdict after reviewPass with notes (rule: Fail if a High finding remains after review, otherwise Pass with notes)

Related: Jupiter developer documentation (May 2026)

0
High after review
4
Medium after review
56
Low after review
2
Info after review
2
excluded on review
How to read this page

Each finding keeps the number it has in the audit report. The rating shown first is the one after review; the first automated rating is listed with it. 43 findings were first rated Medium or High; 4 of them are Medium or High after review. Text marked "From the report" is quoted from the audit report; fixes are suggestions and were not tested. Locations are paths inside the fetched copy of the documentation.

Medium after review (4)

14. Medium Integrator-paid gas flow does not tell the backend to validate before co-signing
First rating: Medium · Reviewed rating: Medium · Review: confirmed
Location: src/pages/ultra/add-payer.md:30 (also src/pages/swap/advanced/gasless.md:115, :145)
From the report
Evidence
| **Requires backend signing** | Integrator is required to proxy the request to their backend in order to sign the transaction (partially signed by user) before sending to `/execute` |
Why it matters

The backend signs client-supplied bytes with a funded payer key. A malicious client can swap in instructions that drain the payer.

Suggested fix, not tested

Tell the backend to compare the transaction byte for byte against the one cached by requestId, or to parse it and refuse anything that debits the payer beyond fees and rent.

Review: ultra/add-payer.md:30 says the integrator "is required to proxy the request to their backend in order to sign the transaction (partially signed by user)". swap/advanced/gasless.md:115,145 does the same. Nowhere in either page (grep for validate/verify/compare/requestId finds nothing relevant) is the integrator told to check what the client sent before co-signing with a funded payer key. The only warning (gasless.md:145 Tip) is about users closing accounts, a different vector. This is the most security-relevant omission in the audited copy; it is guidance omission, not a bug, so MEDIUM and not HIGH.

41. Medium Expiry "renewal" through an endpoint that cannot change expiry
First rating: Medium · Reviewed rating: Medium · Review: confirmed
Location: src/pages/trigger/best-practices.md:32
From the report
Evidence
renew by updating the order before it expires via the [update endpoint]
Why it matters

The update body (trigger.yaml:926-943) has no expiresAt field. Orders expire even though the integrator thinks they renewed them.

Suggested fix, not tested

Add expiresAt to the update endpoint, or document cancel-and-recreate.

Review: trigger/best-practices.md:32 recommends 30-day expiry "with periodic renewal" and says to renew "by updating the order before it expires via the update endpoint". The update body in trigger.yaml:~917-943 has no expiresAt, and manage-orders.md:44-70 lists the updatable fields per order type with none either. The recommended renewal path does not exist as documented. A stop-loss or take-profit then lapses silently. Funds are recoverable (manage-orders.md:156), which is why this is not higher.

47. Medium Outcome-token swap sample uses 100% slippage
First rating: Medium · Reviewed rating: Medium · Review: confirmed
Location: src/pages/prediction/forecast.md:148
From the report
Evidence
slippageBps: "10000",
Why it matters

A real-funds swap is executed with no price protection, which invites sandwich attacks and very bad fills.

Suggested fix, not tested

Use a bounded value, explain the risk, and check the quoted output before signing.

Review: prediction/forecast.md:148 sets slippageBps: "10000" (100%) on a real-funds USDC swap into an outcome token, with the prose "Use a high slippageBps". A copy-pasted sample with no price protection is an unsafe default whatever the managed landing path does. I could not check from the audited copy whether /execute adds MEV protection, so I did not go higher.

56. Medium Kit sample calls a confirmation method the RPC client does not have
First rating: Medium · Reviewed rating: Medium · Review: confirmed
Location: src/pages/transaction/submit.md:456 (also swap/build.md:390, swap/build/index.md:390)
From the report
Evidence
const confirmation = await rpc
  .confirmTransaction(signature, {
Why it matters

The kit RPC client has no confirmTransaction method. The sample throws after the transaction has already been sent, so the developer has no confirmation path.

Suggested fix, not tested

Use the kit's send-and-confirm factory, or poll getSignatureStatuses until lastValidBlockHeight passes.

Review: transaction/submit.md:456 (same in swap/build.md:390, swap/build/index.md:390) calls rpc.confirmTransaction(...).send() in the @solana/kit tab. A kit RPC has no such method (it is a JSON-RPC proxy, so the call is sent to the node as an unknown method and fails), and strategy: {type: "blockhash"} is not a kit option. The web3.js tab of the same page (submit.md:223) uses the real Connection.confirmTransaction. The sample throws only after the transaction was sent, so a naive retry could swap twice. Three pages and the primary /build flow keep this at MEDIUM; it would be LOW if only one page had it.

Low after review (56)

1. Low Trigger quick starts use the deprecated 24-hour token mode
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/trigger/create-order.md:69 (also src/pages/trigger/dca.md:87)
From the report
Evidence
const { token } = await fetch(`${BASE}/auth/verify`, { ...
  body: JSON.stringify({ type: "message", walletPubkey: owner, signature: bs58.encode(signature) }) })
Why it matters

The authentication page says access_only is deprecated and will be removed. These rewritten quick starts omit authMode and read token. Any integration copied from them stops working when the mode is removed. The "24h JWT" comments at lines 60 and 78 repeat the old model.

Suggested fix, not tested

Send authMode: "access_refresh", read accessToken/refreshToken/expiresAt, and add refresh-on-401 handling. You can also link to the migration section.

Review: authentication.md:11 says access_only "is still the default ... Nothing breaks today". Stale teaching, nothing fails.

2. Low Integration-flow page says there is no refresh endpoint
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/trigger/lifecycle.md:53 (also line 22)
From the report
Evidence
The token is valid for 24 hours. There is no refresh endpoint: when it expires, repeat the challenge-response flow.
Why it matters

This contradicts the authentication page and the spec, which document /auth/refresh, /logout and /logout-all. Integrators following this page build a flow that forces wallet re-signing and never adopts the new mode.

Suggested fix, not tested

Describe the 15-minute access token with a rotating refresh token. Update the diagram label "token · JWT 24h", and mention access_only only as deprecated.

Review: The false sentence is real (:53), but it matches the access_only mode the quick starts use and re-sign is a working fallback. Same root cause as 1 and 3.

3. Low 401 guidance says re-authenticate, not refresh
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/trigger/best-practices.md:51 (also trigger/errors.md:33, trigger/index.md:121)
From the report
Evidence
**Authentication errors** (401) mean the JWT has expired. Re-authenticate with a new challenge-response flow rather than retrying the same token.
Why it matters

With 15-minute access tokens, this sends users through a wallet signature every 15 minutes. It contradicts authentication.md:223 and :314 ("refresh once and retry").

Suggested fix, not tested

On a 401, call /auth/refresh once and retry. Start a new challenge only if the refresh itself returns 401.

Review: Real contradiction with authentication.md:223,314. Costs extra wallet prompts, not failure.

4. Low Deprecation of the old auth mode has no removal date
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/pages/trigger/authentication.md:15
From the report
Evidence
**`access_only` is deprecated (2026-07-21) and will be removed.** ... Nothing breaks today
Why it matters

Integrators cannot plan the migration window, and the old mode is still the default.

Suggested fix, not tested

Publish the enforcement date and the date the default flips to access_refresh.

Review: Line says "deprecated (2026-07-21) and will be removed".

5. Low Wallet-auth samples sign the server challenge without checking it
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/pages/trigger/authentication.md:117
From the report
Evidence
const signedTx = await wallet.signTransaction(
  VersionedTransaction.deserialize(Buffer.from(challenge.transaction, 'base64'))
);
Why it matters

The page's own security note says to verify the challenge before signing, but the sample signs blindly. A tampered challenge transaction would be signed.

Suggested fix, not tested

Before signing, confirm that the transaction holds only the memo instruction, that the user is the fee payer and that it has no transfers. For the message variant, confirm the wallet and timestamp.

Review: Sample as quoted; page elsewhere says verify first.

6. Low Browser-wallet samples do not handle a rejected signature
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/pages/trigger/manage-orders.md:132 (also trigger/deposit.md:120, trigger/authentication.md:92)
From the report
Evidence
const signedTransaction = await wallet.signTransaction(transaction);
...
signedTransaction: Buffer.from(signedTransaction.serialize()).toString('base64'),
Why it matters

A user cancel or a silent rejection surfaces as an unhandled exception mid-flow.

Suggested fix, not tested

Wrap the call in try/catch, and stop before confirm-cancel or deposit when the user rejects.

Review: Also deposit.md:120, authentication.md:92 exist.

7. Low Claim-all sample crashes on positions that need no claim
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/prediction/claim-payouts.md:256 (note at line 151)
From the report
Evidence
const transaction = VersionedTransaction.deserialize(
  Buffer.from(claimResponse.transaction, 'base64')
Why it matters

The new note says some markets return a "no claim required" message and no transaction. Buffer.from(undefined) throws, which aborts the loop, so no later position is claimed.

Suggested fix, not tested

Check that claimResponse.transaction is a string before deserializing. Skip and log any other response.

Review: The throw needs a position that is claimable yet needs no claim; the page does not say that combination occurs.

8. Low Prediction wire changes shipped without a migration notice
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/diffs/prediction/social-features.md.diff:24
From the report
Evidence
-| Follow | `POST /follow/{pubkey}` | Follow a trader |
-| Unfollow | `DELETE /unfollow/{pubkey}` | Unfollow a trader |
Why it matters

Several breaking changes were made silently: follow endpoints removed, the claim blockhash moved into txMeta, and status values dropped. Stale text remains, such as "follow traders" at prediction/position-data.md:310, so existing clients break without warning.

Suggested fix, not tested

Publish a change note that lists each breaking change and its date, and remove the stale references.

Review: Follow rows removed, stale "follow traders" at position-data.md:310 is real. Pages carry a beta breaking-change warning.

9. Low Prediction samples sign API transactions without checking payer and signers
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/pages/prediction/forecast.md:61
From the report
Evidence
const tx = VersionedTransaction.deserialize(Buffer.from(build.transaction, "base64"));
tx.sign([wallet]);
Why it matters

The integrator never confirms that the fee payer and transfer source are their own wallet, or that requiredSigners are covered.

Suggested fix, not tested

Add a short pre-sign check of fee payer, transfer source and required signers.

10. Low Outcome token swapped without checking its token program
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/pages/prediction/forecast.md:145
From the report
Evidence
outputMint: market.outcomeMint,
Why it matters

The page documents the outcome token as Token-2022 and the API exposes outcomeTokenProgram, but the sample trusts the returned mint unchecked.

Suggested fix, not tested

Check that outcomeTokenProgram is the Token-2022 program and that the mint is the expected one before building the swap.

11. Low DEX CPI guide does not tell integrators to pin program accounts
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/lend/dex/cpi.md:67 and :107
From the report
Evidence
The Liquidity Layer accounts are validated inside the Liquidity Layer CPI; you don't verify them yourself.
let cpi_ctx = CpiContext::new(ctx.accounts.dex_program.to_account_info(), cpi_accounts);
Why it matters

If an integrator's program accepts a caller-supplied dex_program or token program, that program is never checked. In the PDA-signing variant, the program's signature can then be spent on a malicious program, which risks the funds the PDA controls.

Suggested fix, not tested

State that the DEX, Liquidity Layer, oracle and token programs must be constrained to their published ids, for example Program<'info, Dex> or an address = constraint. Show these constraints in the example accounts struct.

Review: Hardening omission is real, but the MySwap accounts struct is never shown, so no insecure code is taught. The "you don't verify them yourself" sentence covers Liquidity Layer accounts only.

12. Low Old gateway retirement has no dated phases
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/pages/portal/migration.md:16
From the report
Evidence
| Old API gateway (including `lite-api`) progressively deprecated | When new gateway is stable.
Why it matters

Integrators cannot plan the cut-over.

Suggested fix, not tested

Publish a warning window and an enforcement date.

13. Low Legacy submit endpoint superseded without a timeline
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/pages/transaction/submit.md:579
From the report
Evidence
The legacy REST endpoint `POST https://api.jup.ag/tx/v1/submit` still accepts the same signed transactions, but `tx.jup.ag` is the recommended path going forward.
Why it matters

There is no sunset date, the new path requires an API key, and swap.md:28 and :32 still name /submit.

Suggested fix, not tested

Publish the deprecation timeline, state the key requirement, and update the stale references.

Review: swap.md:28,32 name /submit as stated.

15. Low payer example is Jupiter's own sponsorship wallet
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/pages/openapi-spec/swap/v2/swap.yaml:240
From the report
Evidence
example: gasTzr94Pmp4Gf8vknQnqxeYxdgwFjbgdJa4msYRpnB
Why it matters

The gasless page uses this same address as the marker for automatic sponsorship. A copied request is ambiguous and cannot be co-signed by the integrator.

Suggested fix, not tested

Use a placeholder such as YOUR_PAYER_WALLET.

16. Low Confirmation uses a fresh blockhash, not the transaction's own
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/pages/prediction/open-positions.md:118 (also prediction/claim-payouts.md:261, prediction/manage-positions.md:184)
From the report
Evidence
const blockhashInfo = await connection.getLatestBlockhashAndContext({ commitment: "confirmed" });
...
lastValidBlockHeight: blockhashInfo.value.lastValidBlockHeight,
Why it matters

The API now returns txMeta.blockhash and txMeta.lastValidBlockHeight for the signed transaction. Confirming against a later blockhash reports expiry later than it really happens.

Suggested fix, not tested

Confirm with the response's txMeta values.

Review: Also claim-payouts.md:261, manage-positions.md:184.

17. Low Order polling guidance has no interval, terminal states or cap
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/pages/trigger/best-practices.md:74
From the report
Evidence
Poll the history endpoint to track order state; the states differ by family.
Why it matters

With a 1 RPS shared plan, unbounded polling consumes the rate budget.

Suggested fix, not tested

State an interval with backoff, the terminal states per order family and a maximum duration.

18. Low Token refresh is not shared across parallel requests
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/trigger/authentication.md:228
From the report
Evidence
if (res.status === 401) {
  await refreshTokens();          // updates accessToken/refreshToken, or re-authenticates on 401
Why it matters

Each request that gets a 401 starts its own refresh with the same rotating refresh token. The page says that reusing a refresh token outside a short grace window revokes the whole session, so parallel requests can log the user out. refreshTokens() is also left undefined.

Suggested fix, not tested

Share one in-flight refresh promise that every caller awaits. Store the new pair atomically, and coordinate across tabs if the tokens are persisted.

Review: The page itself (:~217) tolerates repeats of the same /refresh inside a "short grace window", which is what parallel callers produce. refreshTokens() undefined is a sketch gap.

20. Low Market resolveAt type changed and metadata removed in place
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/diffs/api-reference/prediction/get-market.md.diff:47
From the report
Evidence
-          type: number
-          description: Unix timestamp (seconds) when the market resolved (null if pending)
-        metadata:
Why it matters

Parsers written against the old contract fail on the same /prediction/v1 path. The new spec allows string | number, which leaves clients guessing.

Suggested fix, not tested

Pick one type, add new keys rather than repurpose old ones, and announce the change.

Review: Beta; the new spec at least states "ISO 8601 string once resolved". string | number looseness is the residual.

21. Low Orderbook sort order reversed
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/openapi-spec/prediction/prediction.yaml:2302
From the report
Evidence
description: YES-side levels sorted by price ascending.
Why it matters

The baseline said "descending". Code that reads yes[0] as the best price now reads the worst level.

Suggested fix, not tested

State which end holds the best bid, and announce the change. Prefer a new field to reversing an existing one.

Review: "ascending" is now stated identically in 3 places (events-and-markets.md:254, get-orderbook.md:61,73). Whether the API changed or the docs were corrected cannot be told; the missing "best price is at which end" line is the real gap.

22. Low Trade id example is a number, the spec says string
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/prediction/social-features.md:144
From the report
Evidence
"id": 425993,
Why it matters

The spec now types id as a string ("order-1906029"). Clients built from the example parse it as an integer and fail.

Suggested fix, not tested

Update the example and the field table to string ids.

Review: Real stale example (get-trades.md:54 says "order-1906029").

23. Low 64-bit PnL amount typed as a plain JSON number
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/pages/openapi-spec/prediction/prediction.yaml:862
From the report
Evidence
realizedPnlUsd:
  type: number
  description: Realized PnL in micro USD (i64 as number)
Why it matters

Sibling amounts are strings, and forecast.md tells readers never to use Number for amounts. Values above 2^53 lose precision.

Suggested fix, not tested

Return the amount as a string.

24. Low Studio maxQuoteAmount is a number while sibling amounts are strings
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/pages/openapi-spec/studio/studio.yaml:568
From the report
Evidence
maxQuoteAmount:
  type: number
Why it matters

The fee endpoint returns base-unit amounts as strings, but this request takes a number with no stated unit, which risks precision and unit errors.

Suggested fix, not tested

Accept a base-unit string and state the unit.

25. Low Borrow ids are market-scoped, but responses never carry the market
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/pages/lend/borrow/api.md:59
From the report
Evidence
Vault and position IDs are scoped to a market. The same `vaultId` refers to a different vault in `main` than in `ethena`
Why it matters

A position id alone is ambiguous, and an operate call that omits market silently uses main.

Suggested fix, not tested

Echo market in every vault, position and operate response. Document it in the operate payload table.

26. Low Client echoes server-computed payment amount back
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/pages/openapi-spec/tokens/v2/verification.yaml:262
From the report
Evidence
description: Atomic JUP output from the craft quote (CraftTxnResponse.amount), echoed back so revenue tracks the actual JUP collected on swap paths.
Why it matters

The server already has requestId, so a client-supplied value can be wrong or forged.

Suggested fix, not tested

Look the value up server-side by requestId, and reject mismatches.

Review: Field at :262, quoted description at :264.

27. Low Deprecated Ultra page still sells itself as the place to start
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/ultra.md:7 (same in ultra/index.md)
From the report
Evidence
> Jupiter's flagship swap API: ... Start here for most swap integrations.
  **Ultra Swap API** is no longer actively maintained and has been superseded by [Swap V2](/docs/swap).
Why it matters

New integrators are told to start on an unmaintained API.

Suggested fix, not tested

Rewrite the tagline and intro to point to Swap V2 and the migration guide.

Review: Tagline "Start here" is wrong, but a Warning box four lines below says "no longer actively maintained".

28. Low Recurring pages still teach price-based orders
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/recurring.md:26 (also lines 36-37, recurring/get-recurring-orders.md:63)
From the report
Evidence
| **Price-based recurring** | Create price-based recurring orders that execute when certain market conditions are met. |
...orderStatus=history&recurringType=price
Why it matters

The sub-pages say price-based orders are no longer supported and that price is deprecated, yet the overview and the history sample use them.

Suggested fix, not tested

Remove or mark the price-based rows and steps, and use recurringType=time in the sample.

Review: The whole Recurring section carries a deprecation warning; sub-pages mark price-based deprecated. Overview rows and the recurringType=price sample (get-recurring-orders.md:63) are stale.

29. Low Swap build samples use the legacy submit endpoint
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/swap/build.md:375 (also line 507 and swap/build/index.md)
From the report
Evidence
const submitRes = await fetch("https://api.jup.ag/tx/v1/submit", {
...
const { signature } = await submitRes.json();
Why it matters

The submit page names tx.jup.ag as the recommended path, and the REST body is no longer documented anywhere. The sample also never checks submitRes.ok, so an error yields signature === undefined.

Suggested fix, not tested

Switch to the tx.jup.ag sample from the submit page, or label these samples legacy. Check the response status.

Review: submit.md:579 says the legacy endpoint "still accepts the same signed transactions"; portal/api-keys.md:51 treats it as live. Missing submitRes.ok check is a sample nit.

30. Low Pricing migration guide still gives a deadline that has passed
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/portal/migration.md:15 and :68 (also portal/faq.md:55, :59)
From the report
Evidence
| Grace period ends and new payment method needs to be already set up | 30th June 2026 |
You must choose a paid plan before the grace period ends or your rate limit will drop to the Free tier (1 RPS).
Why it matters

The docs are dated 2026-10-11. Readers cannot tell what limit applies to them now.

Suggested fix, not tested

Rewrite in the past tense and state the current limits for keys that were not migrated.

Review: 30 June 2026 is in the past, so the page is stale (faq.md:55,59 same). A historical timeline, not a trap.

31. Low Plugin FAQ links to the unmaintained Ultra fee guide
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/pages/tool-kits/plugin/faq.md:18
From the report
Evidence
You can create via [scripts](/docs/ultra/add-fees-to-ultra) or [Referral Dashboard](https://referral.jup.ag).
Why it matters

Current guidance routes readers into the deprecated section.

Suggested fix, not tested

Link to the current referral setup.

32. Low Sitemap lists a page that returns 404
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/DELTA_MANIFEST.md:384
From the report
Evidence
| FETCH-FAILED | `mcp.md` | 404 | yes | sitemap | URL listed in current sitemap.xml but returns HTTP 404
Why it matters

The sitemap entry leads crawlers and readers to a dead page.

Suggested fix, not tested

Remove the entry or redirect it to the MCP pages, and add a release check that every indexed URL returns 200.

33. Low Live pages missing from the published indexes
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/DELTA_MANIFEST.md:21
From the report
Evidence
It found 19 live pages that are in no index: the deprecated Ultra section ... plus `skill`.
Why it matters

The indexes and the live site disagree, so AI tools and search miss the pages or index them without a deprecation label.

Suggested fix, not tested

List them with an "unmaintained" label, or unpublish them and redirect.

34. Low Portfolio API pages removed without notice
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/DELTA_MANIFEST.md:48
From the report
Evidence
Portfolio (6 pages ...) all redirect to `get-started`: the Portfolio API docs are gone.
Why it matters

No page in the delta mentions the retirement. (The changelog moved out of the docs and was not checked.)

Suggested fix, not tested

Add a retirement notice that gives the date and the alternatives.

35. Low Credit table drops endpoints that are still live
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/diffs/portal/plans.md.diff:35
From the report
Evidence
-| `/ultra/v1/order` | 1 |
-| `/tokens/v2/content` | 1 |
Why it matters

Users of still-documented endpoints cannot find what those calls cost.

Suggested fix, not tested

Keep a labelled group for these endpoints while their pages remain live.

Review: tokens/v2/content rows are at diff :72.

36. Low AI skills page lists Recurring as current
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: src/pages/ai/skills.md:23
From the report
Evidence
| Recurring | Dollar-cost averaging (DCA) strategies |
Why it matters

It contradicts the move of DCA to the Trigger API.

Suggested fix, not tested

Point the DCA row to Trigger.

38. Low Price-order cancel rules contradict each other
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/trigger/lifecycle.md:118
From the report
Evidence
* **`open`** orders can be updated or cancelled. Updates and cancellation are only valid from this state.
* **`expired`** orders ... retrieved with the same cancel flow (see below).
Why it matters

Integrators may block withdrawal of expired orders, which strands the user's funds in the vault UI.

Suggested fix, not tested

List every state that accepts cancel (open, expired, and mid-cancel), and say that confirm-cancel may be retried.

Review: One over-broad sentence ("only valid from this state"); the next bullet (:120), :159, index.md:105 and manage-orders.md:156 all say expired orders use the cancel flow.

39. Low Vault registration spec contradicts its own 409 response
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/openapi-spec/trigger/v2/trigger.yaml:641
From the report
Evidence
Call this once per wallet. Subsequent calls return the existing vault.
'409': description: Vault already exists
Why it matters

Clients that follow the description treat the 409 as fatal.

Suggested fix, not tested

Document the 409 together with the returned vault details, matching deposit.md:45 and errors.md:65.

Review: Spec contradiction is real; deposit.md:44 and errors.md:65 explain the 409 and the quick starts call GET /vault first.

40. Low Withdraw guide names the deposit endpoint
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/recurring/withdraw-price-order.md:31
From the report
Evidence
Choose the order account to deposit by making a post request to the `/priceDeposit` endpoint
Why it matters

Following the text on a funds-moving flow builds a deposit, not a withdrawal.

Suggested fix, not tested

Use /priceWithdraw and "withdraw from".

Review: Prose copy-paste error on a deprecated endpoint; the code sample on the same page (:52) correctly calls /priceWithdraw.

43. Low Prediction order status values disagree across pages
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/prediction/open-positions.md:168 vs src/pages/prediction/position-data.md:180
From the report
Evidence
| `created` | Order account created on-chain, waiting to be filled |
| `status` | `pending`, `filled`, or `failed` |
Why it matters

Clients that switch on pending never match. The spec enum and prediction.md:117 disagree as well.

Suggested fix, not tested

Use one status set everywhere: created, partiallyfilled, filled, failed.

Review: Real: created/partiallyfilled vs pending in position-data.md:180, prediction.md:117, spec enum :390. /orders/status has an untyped status string, so two endpoints may legitimately differ.

44. Low Buy table documents contracts, which the spec says is sell-only
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/prediction/open-positions.md:44
From the report
Evidence
| `contracts` | string | No | Number of contracts to purchase |
(spec line 687: Legacy whole-contract sell quantity (only when `isBuy` is `false`))
Why it matters

Buy requests are sized wrongly or rejected.

Suggested fix, not tested

Size buys with depositAmount, and document the sell-only quantity fields.

Review: Real, but the field is marked optional ("No") and the sample sizes the buy with depositAmount.

45. Low Execute path example contradicts its own description
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/openapi-spec/prediction/prediction.yaml:718
From the report
Evidence
description: Relative execute path. Resolves to `https://api.jup.ag/prediction/v1/execute`.
example: /api/v1/execute
Why it matters

A client that resolves the example calls a URL that does not exist.

Suggested fix, not tested

Make the example match the description, and state the base URL.

Review: The description states the full URL; the example is likely the raw relative value the server returns. Same text in 3 reference pages.

46. Low Forecast discovery sample uses tag, but the API takes tags
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/prediction/forecast.md:87
From the report
Evidence
`${PREDICTION_API}/events?provider=bisonfi&category=crypto&tag=15m&includeMarkets=true`
Why it matters

The filter is ignored, and the sample returns every crypto event.

Suggested fix, not tested

Use tags=15m.

Review: Typo confirmed against spec :1448-1451 (name: tags). The sample still filters to provider=bisonfi and tradable. Returns extra markets only.

48. Low Fill quantities documented only as the floored legacy field
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/prediction/position-data.md:181
From the report
Evidence
| `filledContracts` | Number of contracts executed |
Why it matters

The same page warns that unsuffixed contract fields are floored. Accounting built on them is wrong for fractional fills.

Suggested fix, not tested

Document the *Micro and *Decimal fields and mark the plain fields as legacy.

Review: Spec has filledContractsMicro/Decimal (prediction.yaml:1104,1107); the guide omits them. index.md:256 already warns the plain fields are floored.

49. Low Market result and resolveAt cannot be null under the spec as written
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/openapi-spec/prediction/prediction.yaml:141-159
From the report
Evidence
result:
  type: string
  nullable: true
  enum: ["yes", "no"]
Why it matters

In OpenAPI 3.0 the enum must include null, and nullable next to a bare oneOf has no effect. Generated clients reject every open market.

Suggested fix, not tested

Add null to the enum and give resolveAt a single nullable type.

Review: Strict-OpenAPI-3.0 pedantry (OAS 3.0.3 wants null in the enum). "Every open market rejected" assumes a strict generator.

50. Low Provider enum differs between request and response
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/openapi-spec/prediction/prediction.yaml:127 vs :1345
From the report
Evidence
- polymarket / - gx / - bisonfi        (Market response)
- kalshi / - polymarket / - bisonfi    (GET /events filter)
Why it matters

Typed clients fail when a returned value is not in the response enum.

Suggested fix, not tested

Share one provider schema.

Review: kalshi in the filter (:1343), gx in the response (:127) is a real mismatch. Failure needs the API to actually return kalshi, which the audited copy cannot show.

51. Low Price page says "null", the API omits the key
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/price.md:35 vs :94
From the report
Evidence
you may face many tokens where price is not available or returns null.
Tokens without a reliable price are **omitted entirely** from the response.
Why it matters

Code that checks for null throws on the missing key.

Suggested fix, not tested

Rewrite the caution to tell readers to check whether the key is present.

Review: The delta itself added the explicit "omitted entirely" section with an id in price check (:94). Only the older caution sentence is stale.

52. Low Rate-limit sample calls a non-existent path and throttles every call
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/portal/rate-limits.md:58 and :62
From the report
Evidence
const response = await fetch("https://api.jup.ag/tokens/v1", {
const remaining = Number(response.headers.get("x-ratelimit-remaining"));
Why it matters

/tokens/v1 is not a documented route. When the header is missing, Number(null) is 0, so every call sleeps 1 s. The request is also never retried after a 429.

Suggested fix, not tested

Use a real /tokens/v2 route, apply the check only when the header is present, and retry with a cap.

Review: /tokens/v1 is undocumented (elsewhere /tokens/v2); Number(null) gives 0 so it sleeps 1s when the header is absent. Illustrative snippet.

53. Low Lend API-vs-SDK page contradicts the new Borrow API
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/lend/api-vs-sdk.md:28
From the report
Evidence
| Vault config and state | - | `getAllVaults`, `getVaultByVaultId` | - |
| Borrow vaults and user positions | `vaults`, `positions` endpoints | ...
Why it matters

Readers are steered to the SDK for data the REST API now serves. The recommendation table at lines 113-116 repeats the error.

Suggested fix, not tested

Add the REST endpoints to the row and the recommendations.

Review: Row 28 shows REST "-", row 29 shows REST vaults, positions; recommendations :113-116 steer to SDK. Steering only.

54. Low Send spec types contradict its examples
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/openapi-spec/send/send.yaml:379 and :76
From the report
Evidence
confirmed:
  type: integer          (examples: confirmed: true)
- Unix timestamp of when the invite will expire   (example: "2025-03-22T10:30:00.000Z")
Why it matters

Generated clients fail to parse responses that match the examples.

Suggested fix, not tested

Make confirmed a boolean and describe expiry as an ISO-8601 string, or change the examples to match the types.

Review: confirmed is integer with true in examples (:223); expiry described as Unix timestamp with ISO example (:73,84). Both real.

55. Low Studio spec marks fee required yet describes a default
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/openapi-spec/studio/studio.yaml:341 and :436
From the report
Evidence
- fee
- If not provided, the default fee will be 100 basis points (1%)
Why it matters

Callers that omit fee on the strength of the description get a 400.

Suggested fix, not tested

Make fee optional, or delete the default text.

Review: Both lines confirmed (required at :341, default text at :436). Which one is wrong is unknown.

58. Low Ultra fee range stated as 5-10 bps, the table says 0-50
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/ultra/fees.md:13
From the report
Evidence
with only 5 to 10 bps of the swap amount as a fee.
| New Tokens (within 24 hours token age) | 50 |
Why it matters

Fee quotes built from the summary are wrong for new tokens and for free pairs.

Suggested fix, not tested

State the real range and point to the table.

Review: Real; table (:25-33) has 0, 2, 5, 10 and 50. Deprecated page; /order returns feeBps.

59. Low Migration guide names the wrong endpoint for result fields
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/swap/migration/metis-to-build.md:55
From the report
Evidence
Use the `/order` response fields (`inputAmountResult`, `outputAmountResult`)
Why it matters

These fields belong to the /execute response, and /build users call neither endpoint.

Suggested fix, not tested

Tell /build users to parse token balance changes.

Review: inputAmountResult is an /execute field (order-and-execute.md:103), not /order. The sentence also offers "or parse token balance changes".

60. Low Ultra wallet setup snippet does not compile
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/ultra/execute-order.md:47 (also ultra/add-fees-to-ultra.md:241)
From the report
Evidence
const wallet = Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY || '')));
Why it matters

There is an extra ) and bs58 is never imported, so the snippet fails on copy-paste.

Suggested fix, not tested

Fix the parentheses and add import bs58 from 'bs58'.

Review: Extra ) and no bs58 import confirmed; also mixes import and require. Visible compile error on a deprecated page.

61. Low Order built for a hard-coded taker, then signed by the user's wallet
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/ultra/add-fees-to-ultra.md:362 (also ultra/add-payer.md:55)
From the report
Evidence
'taker=jdocuPgEAjMfihABsPgKEvYtsmMzjUHeq9LX4Hvs7f3&' +
Why it matters

The signature does not match the required signer, so execution fails.

Suggested fix, not tested

Use taker=${wallet.publicKey.toBase58()} and a payer placeholder.

Review: Taker jdocu... is a placeholder and the call fails at execute without harm. The second cite (add-payer.md:55) is a payer= value, not a taker (taker there is <pass-in-taker-account>), so that half does not apply.

62. Low v1 sample sends even when simulation failed
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/swap/advanced/transaction-versions.md:220
From the report
Evidence
if (simulation.value.err) console.error("Simulation failed:", simulation.value.err);
Why it matters

A transaction known to fail is broadcast with preflight skipped, and the user pays the fee.

Suggested fix, not tested

Stop when the simulation returns an error.

Review: console.error without exit is confirmed; the sibling kit sample exits (submit.md:419). Cost is one tx fee.

63. Low v1 confirmation loop gives up silently
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/swap/advanced/transaction-versions.md:237
From the report
Evidence
for (let i = 0; i < 40; i++) {
  await new Promise((r) => setTimeout(r, 1500));
Why it matters

After about 60 s the loop ends with no error, so "not landed" looks like success.

Suggested fix, not tested

Throw after the loop, or poll until the block height passes lastValidBlockHeight.

Review: Confirmed, no throw after 40 tries. Sample-quality issue.

64. Low nullable beside $ref makes a common null field invalid
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: src/pages/openapi-spec/swap/v2/swap.yaml:616
From the report
Evidence
cleanupInstruction:
  $ref: "#/components/schemas/Instruction"
  nullable: true
Why it matters

OpenAPI 3.0 ignores siblings of $ref, so null, which is returned on the main /build path, fails strict validation. tokens.yaml:406-417 has the same problem.

Suggested fix, not tested

Wrap the reference in allOf, as tipInstruction already does.

Review: Pattern confirmed (tipInstruction uses allOf, :626). Strict-validator issue only; same class as 49.

Info after review (2)

37. Info Fifteen pages are maintained as two hand-synced copies
First rating: Info · Reviewed rating: Info · Review: location checked, kept as rated
Location: src/DELTA_MANIFEST.md:59
From the report
Evidence
| CHANGED | `ai.md` | 2,379 | yes | sitemap | +3/-3 lines |
| CHANGED | `ai/index.md` | 2,379 | yes | llms.txt+llms-full | +3/-3 lines |
Why it matters

The copies are in step today, but every edit must be made twice.

Suggested fix, not tested

Generate one copy from the other, or redirect one to the other.

57. Info Submit page caps size at 1232 bytes while v1 transactions allow 4096
First rating: Medium · Reviewed rating: Info · Review: could not be settled
Location: src/pages/transaction/submit.md:45
From the report
Evidence
| **Transaction size** | Must not exceed the Solana transaction size limit (1232 bytes) |
Why it matters

swap/advanced/transaction-versions.md:13 promotes v1 transactions of up to 4096 bytes. It is unclear whether those can be submitted here.

Suggested fix, not tested

State whether larger v1 transactions are accepted.

Review: Conflict is real on its face (transaction-versions.md:13) but the report itself says "unclear"; the v1 sample sends via plain RPC, not /submit. Needs the live service.

Excluded on review (2)

Findings the review showed to be wrong or a repeat of another finding. They are not counted above.

Want this for your code?

Upload a ZIP or link a public GitHub repo, pick the areas and the depth, and get findings by severity with fixes.