Send a request
Send queries withPOST and Content-Type: application/json to:
query.graphql and the variables to vars.json, then send both with this command:
POST with any other content type returns HTTP 400 with the message transport not supported.
Root queries
A muxed address (
M...) is not accepted. Pass the underlying G... address.
The service does not tell a missing transaction or operation apart from a database failure. Both return null with the message internal server error and the code INTERNAL_SERVER_ERROR.
Pagination
Lists of transactions, operations, state changes, balances and allowances are Relay connections:edges { cursor node } plus pageInfo.
The cap of 100 applies to every connection: account history, balances, SEP-41 allowances, and the connections nested under a transaction or operation.
Edges in a page are always oldest first, in both directions. History connections start at the oldest record, so
first: 10 returns the account’s first ten transactions. For the most recent ten, use last: 10, then before: <startCursor> to step further back.
Treat cursors as opaque strings. Pass a cursor back only to the same field it came from.
Four lists are plain arrays with no pagination.
Transaction.accounts and Operation.accounts return every participant. AccountTransactionEdge.operations returns every operation the account took part in inside that one transaction. AccountTransactionEdge.stateChanges returns the account’s state changes in that transaction, newest first, up to 2,000 (see State changes).
Example: page through an account’s transactions
query.graphql
vars.json
Response
"after" set to endCursor. Stop when hasNextPage is false.
Time bounds
Account.transactions, Account.operations and Account.stateChanges take since and until. Both are RFC 3339 timestamps compared with the ledger close time.
Either bound can be used alone.
until earlier than since returns BAD_USER_INPUT: until must not be before since. Bounds combine with cursors and with the state change filter, and they don’t change a query’s complexity score.
Set a bound when you can: on a long history, a bounded query runs faster. For one day of operations, declare $since: Time, $until: Time, pass them to operations(since: $since, until: $until, first: $first), and send:
vars.json
Balances
accountByAddress(...).balances returns every token balance as one connection. What you get depends on the address:
The types always come in that order. Inside one type the order is stable across pages but is not alphabetical.
Each node implements
Balance with balance, tokenId and tokenType. Use __typename or tokenType to tell them apart, and inline fragments for the rest.
All amounts are strings. Only
SEP41Balance needs scaling: divide balance by 10^decimals to display it. SACBalance.decimals is always 7, and the string already carries the decimal point. tokenId is the token’s contract ID (C...): for XLM and classic assets it is the Stellar Asset Contract ID, and for pool shares it is the hex pool ID.
Spendable XLM is balance - minimumBalance - sellingLiabilities.
Account.sep41Allowances lists the active SEP-41 allowances the account granted, ordered by spender. Expired allowances are left out.
Example: all balance types
query.graphql
vars.json
Response
State changes
A state change records one change to one account’s ledger state: a balance movement, a signer edit, a trustline added, and so on. Each one points to the transaction that caused it and, except for fee charges, the operation.AccountTransactionEdge.stateChanges keeps a transaction’s newest 2,000 rows for the account and drops older ones, so a list of exactly 2,000 may be incomplete. Page Transaction.stateChanges for every row of a large transaction.
filter is an AccountStateChangeFilterInput. All the fields you set must match.
Each node is a concrete type such as
BalanceChange or SignerAddedChange, and category and reason identify the type too. The schema reference lists each type’s valid category and reason pairs.
A fee charge is a BalanceChange with reason DEBIT and a null operation.
Some field names have different types on different state change types. tokenId is String! on BalanceChange and AllowanceChange but nullable on the trustline types, and flags is [AccountFlag!]! on AccountFlagsChange but [TrustlineFlag!] on BalanceAuthorizationChange. GraphQL rejects a query that selects both under the same name, so give each one an alias, as below.
Example: incoming payments to an account
query.graphql
vars.json
Response
BalanceChange.amount is an integer string in the token’s smallest unit. For XLM and classic assets that is stroops, so "250000000" is 25 units.
Transactions and operations
Int64 and UInt32 are JSON numbers. A TOID can be larger than 2^53 - 1, so JavaScript clients should parse responses with a big-integer-safe JSON parser. Timestamps are RFC 3339 strings.
Example: a transaction with its operations and state changes
query.graphql
vars.json
Response
operations and stateChanges on each Account.transactions edge. Those two lists hold only what that account did in the transaction.
Limits
Complexity: each selected field costs 1. A connection multiplies the cost of what you select inside it by
first or last, or by 50 when neither is set. Transaction.accounts and Operation.accounts multiply by 50. The unpaginated lists on AccountTransactionEdge are not multiplied.
Always pass
first or last on nested connections. Inline fragments add up: the service counts every inline fragment you select, even though each node matches only one, so a query that selects fields on every state change type pays for all of them on every edge. For depth, every field adds one level; inline and named fragments add none.
Errors
GraphQL errors come in the standard envelope. Branch onextensions.code; path names the field that failed.
null. When the field is non-null in the schema, the null moves up to the closest nullable parent, so a failure deep inside a transaction can null the whole transactionByHash result.
Persisted queries
The service supports Automatic Persisted Queries (APQ): after the first request, a client can send a 64-character hash in place of the full query text.-
Compute the lowercase hex SHA-256 of the exact query string you send. When the query comes from
query.graphql, hash the file: -
Send the hash with no
query: -
On a miss the response is:
Send the same body again with
"query"set to the query text. The service checks the hash, stores the query and runs it. - Later requests send the hash alone and get the normal result.
version must be 1, and a hash that doesn’t match the query returns INTERNAL_SERVER_ERROR. Each service instance keeps its own cache of 100 queries, so a client should always handle PERSISTED_QUERY_NOT_FOUND by resending the query text.
Adapted from the stellar/wallet-backend documentation (Apache-2.0).