Events feed
Poll EveryPage for reading activity and form submissions using the events API, an alternative to webhooks.
The events feed lets you poll EveryPage for activity on your files without setting up webhooks. Use it for batch processing, backfilling historical data, or integrating with systems that cannot accept inbound requests.
The API
GET /api/v1/events
Returns events across your files as a JSON array. Requires the readership:read scope (an API key holds it). Each event has a stable id that lets clients dedupe and page without gaps. Events belonging to trashed documents are excluded from every stream.
Stream selection
The type parameter selects one of three independent event streams:
| Type | Description | Plan |
|---|---|---|
view | Reading sessions (one row per session) | Free |
download | Explicit save-to-disk downloads | Free |
gate | Completed viewing-gate form submissions | Pro |
Omit type or pass type=view to read the view stream. Any other value returns 400 (unknown type: must be view, download, or gate). There is no type=all—a request must name one stream.
type=gate requires the Pro plan; below it the API returns 403 with the plain-text body This feature requires the pro plan or higher. GET /api/v1/gate-responses is a thin alias of ?type=gate, with the same rows, cursor, parameters, and Pro gate.
Cursors are per-type and not portable across streams. Each stream numbers its rows from its own sequence, so id 500 in the view stream and id 500 in the download stream are unrelated records. Passing a cursor from one stream to another silently returns the wrong window—no error. Maintain a separate since marker per stream.
Pagination
The since parameter controls both the window and the ordering:
since=0, omitted, or unparseable: return the newest events first (descending). Use this for a first call.since=<id>: return events with id greater than<id>, in ascending order. Use this to poll forward without gaps or duplicates.
The limit parameter defaults to 50 and is clamped to the range 1–100—out-of-range and unparseable values are clamped or defaulted, never rejected.
To poll without missing events, store the highest id you have seen in each stream and pass it as since on the next call.
Cursor width differs by stream: view and download ids are 32-bit and a larger since is clamped to 2147483647; gate ids are 64-bit.
Filtering by file
Pass ?file=<identifier> to return events for only one file. The identifier accepts:
- UUID (the canonical file id)
- Short ID (12-character base62 identifier)
The file is ownership-checked. An unknown identifier, or one belonging to another account, returns an empty array ([]) with 200—never a 404 or 403. This is intentional: the parameter must not work as a file-existence oracle. An empty array therefore means "no matching events", not "no such file".
Event shape
Each event is an object. Keys appear in this order:
{
"id": 42,
"fileUuid": "550e8400-e29b-41d4-a716-446655440000",
"fileName": "proposal.pdf",
"readAt": "2024-10-15T14:22:33Z",
"country": "US",
"pagesViewed": 5,
"timeMs": 47000,
"type": "view"
}
| Field | Type | Present on | Notes |
|---|---|---|---|
id | integer | All | Unique within its stream only. Use as the cursor. |
fileUuid | string | All | Canonical file UUID, even when the file was reached by short ID. |
fileName | string | All | The document's original file name. |
readAt | ISO 8601 datetime | All | When the event occurred. Omitted on the rare view row with no recorded start time. |
country | string | view, download | Two-letter country code resolved from the IP at capture time. Omitted when unavailable, and never present on gate events. |
pagesViewed | integer | view | Pages the reader opened. |
timeMs | integer | view | Milliseconds spent on the document. |
type | string | All | "view", "download", or "gate". |
fields | object | gate | String key-value pairs from the submitted form. Omitted when empty or undecodable. |
View rows always carry pagesViewed and timeMs, including when both are 0, so the no-parameter response stays byte-stable for Zapier and similar tools. Download and gate rows omit them entirely. New fields are appended after type, so treat key order as a stable prefix rather than a fixed shape.
Important behaviors
The download stream includes your own downloads
The download stream records every explicit download, including downloads you make yourself as the file's owner. This is a deliberate divergence from the file.downloaded webhook, which never fires for owner actions.
The two sources therefore do not reconcile: a document you downloaded twice yourself shows two extra rows in the feed that the webhook never delivered. Anything that compares or merges the two—backfills, download counters, alerting—has to account for it.
The gate stream excludes plain email captures
The gate stream carries only completed gate form submissions. When a viewer clears a plain email-prompt gate, the address is stored on the view session instead, and no gate event is produced. This exclusion is deliberate: those captures have their own id sequence, and mixing them in would break the single-cursor contract.
To read plain email captures, use the per-file readership endpoint (GET /api/v1/files/{uuid}/readership, Pro), whose recent sessions carry the address as email, or the readership dashboard.
Example: Polling for new views
# Fetch the 10 most recent view sessions
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://everypage.co/api/v1/events?type=view&limit=10"
# Store the highest id from the response, then poll forward
# (results now ascend from the cursor)
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://everypage.co/api/v1/events?type=view&since=12345&limit=10"
Example: Backfilling a single file
# The 50 most recent gate submissions for one file (Pro)
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://everypage.co/api/v1/events?type=gate&file=550e8400-e29b-41d4-a716-446655440000"
To walk the whole history, repeat the call with since=<highest id seen> until it returns an empty array.
Webhooks vs. polling
Choose webhooks if you need low latency—events arrive in your system within seconds. Choose the events feed if you need batch processing (run hourly), backfill (recover missed events), or integration with systems that cannot receive webhooks (many automation platforms, business intelligence tools, data warehouses).
Both methods are supported, and neither is exactly-once: a webhook delivery is retried up to five times, so your endpoint must tolerate repeats, and a polling client re-reads the same rows until it advances its cursor. Dedupe on the event id (feed) or the delivery payload (webhooks).
Learn more about webhooks in Webhooks.
Authentication and limits
Send an API key or an OAuth token in the Authorization: Bearer header. An OAuth token needs the readership:read scope; an API key holds every scope. Like the rest of /api/v1/, the endpoint is limited to 120 requests per minute, keyed on the Authorization header value—so the budget is per token, not per IP.
The gate stream requires the Pro plan. Free and Basic accounts can read the view and download streams.