Skip to content

Read API

Silo exposes its event store to other apps over HTTPS, read-only, at https://event-store.silo.adpharm.digital. There are two ways in, and one key works for both:

  • GET /v1/events — raw event rows, keyset-paginated. For syncing events into your own system.
  • POST /v1/query — any SQL SELECT against the same data, JSON back. For answering a question (counts by day, top pages, a funnel) without pulling everything first. GET /v1/schema tells you what to query.

Every key is scoped to specific sources (sites), and that scope is enforced by the database itself: a query that names a source outside the key’s scope gets zero rows, not an error.

Keys are minted in the Silo console under API keys. A workspace admin picks a name, the sources the key may read, and an optional expiry. The token is shown once — copy it then. Lost it? Revoke and mint a new one; there is no way to see it again.

Send it on every request as a bearer token. Clients that can’t set an Authorization header may use X-Api-Key instead; both carry the same secret.

Terminal window
-H "Authorization: Bearer silo_sk_…"

Send one SELECT or WITH statement (no semicolons, no multiple statements) and, optionally, a row limit.

Terminal window
curl -s https://event-store.silo.adpharm.digital/v1/query \
-H "Authorization: Bearer silo_sk_…" \
-H "content-type: application/json" \
-d '{
"sql": "select date_trunc('"'"'day'"'"', created_at) as day, count(*) as events from organic_events group by 1 order by 1 desc",
"limit": 90
}'
{
"data": [{ "day": "2026-09-09T00:00:00.000Z", "events": "1432" }],
"total_rows": 1,
"returned": 1,
"capped": false,
"limit": 90,
"max_limit": 1000,
"scope": { "source_write_keys": ["my-site"] }
}

limit is a ceiling, not a page: the query is wrapped in LIMIT limit + 1, so capped: true means there was more, and total_rows is then the true count. Page in SQL (ORDER BY … OFFSET, or keyset on id) rather than asking for everything. The default limit is 200 and the maximum is 1000.

The query runs read-only, with a statement timeout, as a database role that can see only your key’s sources. Aggregate in SQL — it is faster and you keep the rows you don’t need on the server.

The schema reference for the query API, as Markdown, behind the same key:

Terminal window
curl -s https://event-store.silo.adpharm.digital/v1/schema \
-H "Authorization: Bearer silo_sk_…"

It lists the two views you can read (organic_events for real people, all_events for everything including bots and internal traffic), every column, the shape of the payload JSON, and the rules that matter — for example that unique users are counted by anonymous_id, and that created_at is the event time. It is generated from the live database definition, so read it from the endpoint rather than copying it; if you are handing SQL-writing to a model, give it this document first.

Terminal window
curl -sG https://event-store.silo.adpharm.digital/v1/events \
-H "Authorization: Bearer silo_sk_…" \
-d source_write_key=my-site \
-d event="Purchase" \
-d from=2026-08-01T00:00:00Z \
-d limit=100

Filters, all optional: source_write_key (narrows within the key’s scope), event, type, from / to (ISO 8601 on created_at; to is exclusive), prop.<path> for a payload property (prop.checkout.step=3), include (organic by default, all for bots and internal traffic too), order (desc by default), and limit (1–1000, default 100).

{
"data": [{ "id": 12345, "created_at": "", "source_write_key": "my-site", "payload": { } }],
"page": { "limit": 100, "order": "desc", "include": "organic", "has_more": true, "next_cursor": "ZGVzYzoxMjM0NQ", "max_limit": 1000 },
"scope": { "source_write_keys": ["my-site"] }
}

Follow next_cursor until has_more is false, keeping order the same while you page. Consecutive from / to windows tile without double-counting.

CodeMeaning
400Bad request. The body carries code (invalid_body or query_rejected) and, for a rejected query, the database’s own message — a misspelt column, a statement timeout, a non-SELECT.
401Missing, malformed, revoked, or expired key. All four answer identically.
403The key is valid but has no sources assigned.
500Something on Silo’s side failed. Logged with the key id, never the token.
503Either that surface isn’t configured on the deployment (code says which part), or Silo is briefly at capacity (code: "over_capacity", with a Retry-After header). Back off for the number of seconds it names and retry the same request.
  • Revoking or rescoping a key in the console takes effect on the next request.
  • A key never sees raw IP addresses or any table beyond the two views.
  • Keep keys server-side. They are read-only, but they are also the only thing standing between the internet and your event data.