Your contract's data isn't in any public API
Public APIs index what every app shares, so your contract's data isn't there. What that leaves you with, and the indexer we built instead.
A few weeks into a Stacks app, you need the screen that shows users their own history: deposits, position over time, share of the pool.
The public API has transfers, blocks and balances. It doesn't have your pool's positions, because nobody else knows your contract exists.
A public API serves what every app on the chain has in common, and that's genuinely useful until your question gets specific. Nobody will build a positions table for a contract only you deployed, and you wouldn't want the one they'd design¹1. It's the same reason your app has its own database instead of sharing one with every other app: a schema built for everybody fits nobody in particular..
That leaves two options, and both cost more than they first appear to.
You can usually get close. Pull the raw transfer list, filter it to your contract, rebuild the shape you wanted in application code, and cache it so the page loads in under four seconds.
Then every new screen needs a slightly different slice, so the rebuilding logic grows. A rate limit shows up in production, and you learn how much of your uptime was someone else's. Once that helper passes a few hundred lines, you've written an indexer on the request path without meaning to.
That's a real project. You run a node or pay for access to one, turn its event firehose into rows, and decode Clarity values into types a database can hold. You backfill years of history before any of it is useful, then keep up with the tip.
You also have to handle reorgs. Blocks near the tip are provisional, and when the network switches branches, your database holds rows for events that no longer happened. Undoing them correctly is harder than writing them was.
Secondlayer does that work, hosted or on your own hardware. It sits next to a Stacks node and turns the chain into decoded rows in a database you operate. History restores from a signed archive in hours instead of a two-week sync, and reorgs roll back cleanly.
You describe the table your app needs:
Deploy it and the table fills itself: backfilled from history, current with the tip, correct across reorgs. Then you query it like any other Postgres table.
The decoding, history and reorg handling are the same either way. The choice is whether you already serve data your own way.
an API layer (tRPC, GraphQL, Rails, something)
Then you don't want ours. Write a consume loop and the rows land in your schema: your tables, your URLs, your auth. Served the way you already serve things.
no API layer
Then describe the table in one file and we'll hand you a REST API over it, running on your instance. The URLs and the filter grammar are decisions we made and you're inheriting.
If you have opinions about your URLs, the first path exists so you can keep them. A generated API is convenient, and it comes with our choices.
You pay for rows delivered: 1M free every month, then $5 per million, live or from history. Checking our work is free: verify the archive, replay it and compare your rows against it at no charge.
Self-host is MIT, with no paid tier above it. Next to your own node, the only thing you'd pay us for is a large restore from the signed archive. Queries against your own database are never billed.
Self-host it beside a node, or start hosted, and point it at your contract.