Document the response differences between Horizon and RPC - #2828
Document the response differences between Horizon and RPC#2828kaankacar wants to merge 6 commits into
Conversation
The migration guide maps each Horizon endpoint to an RPC method, but it
never said that the two responses have different shapes. That gap is the
one people hit first: Horizon aggregates an account with its trustlines
and formats amounts to seven decimal places, while getLedgerEntries
returns one raw ledger entry per key with amounts as int64 stroops.
Add a Response Differences section that compares GET /accounts/{address}
with getLedgerEntries side by side, using real Testnet responses. It
covers response shape, raw amounts, XDR encoding, the flags and
thresholds fields, and the fact that a missing entry is a 200 rather
than a 404.
Also link the migration guide from the APIs overview, which had no
inbound link to it.
There was a problem hiding this comment.
Pull request overview
Adds documentation to help developers migrating from Horizon to RPC understand and handle the different response shapes—specifically by introducing a side-by-side “Response Differences” section in the migration guide and adding an inbound link from the APIs overview.
Changes:
- Add an inbound link from the APIs overview to the Horizon→RPC migration guide.
- Add a “Response Differences” section comparing Horizon
GET /accounts/{address}to RPCgetLedgerEntries, including worked example responses and guidance on amounts, encoding, flags/thresholds, and missing-entry behavior. - Update the endpoint mapping row for
GET /accounts/{address}to point readers to the new example section.
Recommendation: NEEDS-CHANGES — the RPC example response should include entry keys (to support the “missing entry” guidance), and the new XDR reference should be consistent with the rest of the RPC docs.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| docs/data/apis/README.mdx | Adds a direct link to the migration guide from the APIs overview. |
| docs/data/apis/migrate-from-horizon-to-rpc.mdx | Adds a new “Response Differences” section and links it from the endpoint mapping table. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| "entries": [ | ||
| { | ||
| "dataJson": { | ||
| "account": { | ||
| "balance": "90092998203", | ||
| "num_sub_entries": 1, | ||
| "flags": 0, | ||
| "thresholds": "01000000", | ||
| "signers": [] | ||
| } | ||
| }, | ||
| "lastModifiedLedgerSeq": 1397061 | ||
| }, | ||
| { | ||
| "dataJson": { | ||
| "trustline": { | ||
| "balance": "12527872975", | ||
| "limit": "9223372036854775807", | ||
| "flags": 1 | ||
| } | ||
| }, | ||
| "lastModifiedLedgerSeq": 3657583 | ||
| } | ||
| ], |
There was a problem hiding this comment.
🤖 Automated message from Kaan's Automated Triage Bot.
Correct, and fixed. One detail: with "xdrFormat": "json" the field is keyJson, not key. A live Testnet call returns keyJson and dataJson per entry.
Changes:
- The example now shows
keyJsonon both entries. - The missing-entry section names the field to match on (
keyJson, orkeyin base64 format).
| ::: | ||
|
|
||
| [CAP-67]: https://github.com/stellar/stellar-protocol/blob/master/core/cap-0067.md | ||
| [`AccountEntry`]: https://github.com/stellar/stellar-xdr/blob/v28.0/Stellar-ledger-entries.x#L190 |
There was a problem hiding this comment.
🤖 Automated message from Kaan's Automated Triage Bot.
Keeping v28.0. Reasons:
- v28.0 is the latest stellar-xdr tag.
AccountEntryis at line 190 in both v25.0 and v28.0, so the link is correct.- The docs pin no single tag. Other pages use
next,curr, v22.0, v25.0, v27.0 and raw commits.
The v25.0 links on the getLedgerEntries page are older. Re-tagging that page is a separate change.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (3)
Previously missed (2) — in code that hasn't changed since the last review.
docs/data/apis/migrate-from-horizon-to-rpc.mdx:24
- The table says RPC returns a “Signed 64-bit integer”, but in JSON responses these int64 values are typically serialized as strings (as shown in the example snippet). Clarifying this avoids suggesting callers can safely treat the value as a JSON number.
This issue also appears on line 162 of the same file.
docs/data/apis/migrate-from-horizon-to-rpc.mdx:195
- This row uses “trust line” but the docs elsewhere (including this page’s earlier “trustline entries”) consistently use “trustline” as a single term. Aligning terminology will reduce confusion for readers searching the docs.
docs/data/apis/migrate-from-horizon-to-rpc.mdx:162
- “RPC returns the integer itself” is a bit misleading in the
xdrFormat: "json"context, since int64 amounts are usually returned as JSON strings to preserve precision. Tweaking the wording here will align with the example response and the big-number advice.
Every classic amount on the ledger is a signed 64-bit integer of stroops. Horizon divides it by ten million and returns a string with seven decimal places. RPC returns the integer itself. Divide it in your own code, and use a big-number type so you do not lose precision. See [Amount precision](../../learn/fundamentals/stellar-data-structures/assets.mdx#amount-precision).
|
🤖 Automated message from Kaan's Automated Triage Bot. Round 2 had no active comments. Three suppressed comments, all accepted and fixed in 5e0dd14.
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
docs/data/apis/migrate-from-horizon-to-rpc.mdx:166
- This section describes missing keys as being "left out of
entries", but per the RPC OpenRPC schemaentriesitself may be omitted when no entries are found. Mentioning this avoids clients assumingentriesis always an array.
### A Missing Entry Is Not an Error
Horizon answers `404` when a resource does not exist. RPC answers `200` and leaves the key out of `entries`, so a request for two keys can return one entry. Match each returned entry back to its own key with the entry's `keyJson` field, or `key` in the default base64 format. Do not match on the position you sent it in.
| | Encoding | Parsed JSON fields | Base64 XDR, or JSON when you pass `xdrFormat` | | ||
| | Flags and thresholds | Named booleans and named thresholds | A raw `uint32` bitmask and a four-byte hex string | | ||
| | Entry not found | HTTP `404` | HTTP `200`, and the key is absent from `entries` | | ||
| | Collections | HAL `_links` and a `paging_token` for paging | No paging, and a limit of 200 keys per call | |
There was a problem hiding this comment.
🤖 Automated message from Kaan's Automated Triage Bot.
Partly correct. Fixed in 964c565.
- Accepted: the missing-key case needed the all-missing shape. The table and the section now say
entriesis empty when no requested key exists. - Rejected: "omitted entirely". A live Testnet call with one nonexistent account key returns
{"entries":[],"latestLedger":4562786}. The array is present and empty. - The schema point is real, so the section now says the specification requires only
latestLedgerand tells the reader to readentriesdefensively.
| } | ||
| ``` | ||
|
|
||
| RPC answers with one entry per key. Each entry repeats the key it answers, in the `keyJson` field. This is the `result` object of that response, trimmed the same way: |
There was a problem hiding this comment.
🤖 Automated message from Kaan's Automated Triage Bot.
Correct, and fixed in 2da35bb. The same wording was in the comparison table, so both changed.
- "one entry per key" is now "one entry for each key it finds".
- The Shape row is now "One ledger entry for each requested key that exists".
| | Amounts | Decimal string with seven places, such as `"1252.7872975"` | Signed 64-bit integer of stroops, sent as a string, such as `"12527872975"` | | ||
| | Encoding | Parsed JSON fields | Base64 XDR, or JSON when you pass `xdrFormat` | | ||
| | Flags and thresholds | Named booleans and named thresholds | A raw `uint32` bitmask and a four-byte hex string | |
There was a problem hiding this comment.
🤖 Automated message from Kaan's Automated Triage Bot.
Correct, and fixed in 3103cda. One line under the table now says the RPC column describes the decoded entry, and that a default response keeps those fields inside the base64 XDR. This covers the Amounts row and the Flags and thresholds row together.
This is the last fix round on this PR. Later Copilot rounds get an adjudication reply, not a push.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (2)
docs/data/apis/migrate-from-horizon-to-rpc.mdx:27
- The table implies
entrieswill always be present and empty when nothing is found, but the OpenRPC schema only requireslatestLedgerin the result, so clients should also tolerateentriesbeing omitted. Consider softening this row to avoid guaranteeingentriesis present.
| Entry not found | HTTP `404` | HTTP `200`, and the key is absent from `entries`, which is empty when no key exists |
docs/data/apis/migrate-from-horizon-to-rpc.mdx:168
- This paragraph states
entriesis empty when no key exists, but per the OpenRPC schemaentriesis optional (onlylatestLedgeris required). Updating the wording to “empty or omitted” keeps the doc aligned with the spec and matches the recommendation to readentriesdefensively.
Horizon answers `404` when a resource does not exist. RPC answers `200` and leaves the key out of `entries`, so a request for two keys can return one entry. If no key exists, `entries` is empty. The specification requires only `latestLedger` in the result, so read `entries` defensively.
|
🤖 Automated message from Kaan's Automated Triage Bot. Verified: every value in both examples matches a live Testnet call, the XDR and OpenRPC claims match their sources, and the preview renders with all links resolving. The body says "Refs #1621", which is right, because only the account endpoint is covered. Ready to merge. |
ElliotFriend
left a comment
There was a problem hiding this comment.
the content here looks fine to me. my main question is if this is the right place on the page for this section? the table seems like the most "useful" content on the page, and this is maybe a good fit to follow that information? we could (and should) link to it from the (early on) "request/response format" section, but i'm not sure about displacing the table so heavily.
🤖 Automated triage bot, acting for @kaankacar.
Refs #1621. The migration guide maps each Horizon endpoint to an RPC method, but it never said the two responses have different shapes. This adds a Response Differences section that compares
GET /accounts/{address}withgetLedgerEntriesside by side, using real Testnet responses, and covers response shape, raw stroop amounts, XDR encoding, the flags and thresholds fields, and the missing-entry case. It also links the migration guide from the APIs overview, which had no inbound link to it.This uses "Refs", not "Closes". The issue asks for a comparison of the most-used Horizon endpoints, and this delivers the account endpoint only. Please close the issue when the remaining endpoints are covered.
Verified against live Testnet data and primary sources:
9009.2998203XLM equals the account entry's90092998203stroops. Horizon1252.7872975USDC equals the trustline entry's12527872975.AccountEntryin stellar-xdr v28.0 has no trustline field and commentsint64 balanceas "in stroops".thresholdsreads[weight of master|low|medium|high], which matches the live"01000000"next to Horizon's master key signer of weight 1.TrustLineFlagssetsAUTHORIZED_FLAG = 1, which matches the liveflags: 1and Horizon'sis_authorized.xdrFormat: "json"shape is not stable.entries. Horizon returns 404 for the same account.