Skip to main content
Keep a local copy of an account’s Activity history that you can query with SQL. The sync walks the history oldest first, stores each page of records together with the link to the next page, and resumes from that link after a crash or restart. Rerunning it later fetches only the new ledgers. Set these once in your terminal. The sample values are real Testnet data, so the read examples return real results.
This recipe syncs a Testnet wallet with a few hundred records:

Tables

Two tables: the events, keyed so a record can never be stored twice, and the sync progress per account. The examples run this schema on SQLite so they work as pasted; it is also valid PostgreSQL (use JSONB for body there).
(network, id) is the same key webhooks use for the same event, so you can add pushes to this table later. After a Testnet reset, ledger numbers and IDs repeat: start a new database.

How it works

  1. Start. With no saved state, request order=asc from the first ledger. With a saved scanned_through_ledger S, request since_ledger=S+1. With a saved next_href, resume from it.
  2. Store each page in one transaction. Insert the records with ON CONFLICT DO NOTHING and save the page’s _links.next.href in the same transaction, so a crash never loses or duplicates a page.
  3. Finish. When range_complete is true, save scanned_through_ledger and clear next_href. Use scanned_through_ledger, not the ledger of the last record: the scan covers ledgers where the account had no activity.
  4. Retry only what is safe. rate_limited (429) and busy, index_not_ready or unavailable (503) are retried with the same URL after Retry-After seconds, at most 5 times. A Retry-After over 60 seconds means the daily Testnet cap is reached: stop and run again after 00:00 UTC. query_timeout is never repeated unchanged: the sync stops, and you rerun it with a lower limit. Every other error stops the sync; the saved link resumes it.

Run the sync

The first run walks the whole history; a run a minute later fetches only the new ledgers:
Then query your copy:

Keep it current

Run the sync on a schedule, for example every minute. Each run starts at scanned_through_ledger + 1. When nothing new is published yet, the page is empty and complete and the saved ledger stays the same; that is not an error. For a busy account, raise limit to 1000 (the account route’s maximum) to need fewer pages. For pushes instead of polling, add a webhook subscription; its events land on the same (network, id) key.

Credits

On Testnet the sync is free: each page is one request toward your daily Testnet cap of 10,000 requests. A sync every minute that finds nothing new uses 1,440 requests a day per account. On Mainnet, account activity costs 1 credit per started 100 records (and an empty page costs 1). The first run above would cost 3 credits. A sync every minute costs at least 1,440 credits a day per account, about 43,200 a month, so the Free plan’s 250,000 credits cover about five accounts. Sync less often, or add a webhook and sync only to fill gaps. See Limits and credits.

Next