GraphQL
Read a site's raw events through GraphQL, with the same keys, limits and plan as the raw event API, and the schema, examples and errors.
Reference4 min read
GraphQL reads the same events as the raw event API, with the same keys. It comes with the Growth plan.
https://api.bifrostorbit.com/insights/graphqlhttps://api-stage.bifrostorbit.com/insights/graphql| Address | |
|---|---|
| Production | https://api.bifrostorbit.com/insights/graphql |
| Staging | https://api-stage.bifrostorbit.com/insights/graphql |
https://app.bifrostorbit.com/insights/api/cdn/graphql is the same endpoint.
GraphQL only reads. Send events with the script or the server API.
Requests
Send a key that reads events, starting with ins_live_, as Authorization: Bearer <key> or x-api-key: <key>, and POST a JSON body:
queryvariablesoperationName| Property | Type | Means |
|---|---|---|
query | string | The GraphQL document. Required. |
variables | object | Values for the query's variables |
operationName | string | Which operation to run when the document has several |
curl 'https://api.bifrostorbit.com/insights/graphql' \ -H 'Authorization: Bearer ins_live_…' \ -H 'Content-Type: application/json' \ -d '{"query":"query ($site: String!) { events(site: $site, from: \"2026-10-01\", to: \"2026-10-02\", type: \"event\") { items { name at path revenue currency } next } }","variables":{"site":"SITE_KEY"}}'GET works too, with query, variables (as JSON text) and operationName in the query string. URL-encode them.
Answers are JSON with data, and errors when something went wrong:
{ "data": { "events": { "items": [{ "name": "Booked", "at": "2026-10-01T09:14:03.000Z", "path": "/checkout/", "revenue": 1240, "currency": "EUR" }], "next": null } } }Keys belong on your server or data tool, never in a web page, so the endpoint answers servers only.
Queries
eventssite: String!, from, to, name, type, limit: Int, cursorEventPage!. A site's events, oldest first. The arguments work as the parameters of GET/events: without from and to, the last 24 hours; limit is 100 by default and at most 1,000.| Query | Arguments | Returns |
|---|---|---|
events | site: String!, from, to, name, type, limit: Int, cursor | EventPage!. A site's events, oldest first. The arguments work as the parameters of GET/events: without from and to, the last 24 hours; limit is 100 by default and at most 1,000. |
Types
EventPageitems: [Event!]!, and next: pass it as cursor, with the same other arguments, for the next page. null on the last page.Eventid, type, name, at, host, path, referrer, channel, utm { source medium campaign term content }, country, device, browser, os, properties, revenue, currency, contentId, label, visit, person| Type | Fields |
|---|---|
EventPage | items: [Event!]!, and next: pass it as cursor, with the same other arguments, for the next page. null on the last page. |
Event | id, type, name, at, host, path, referrer, channel, utm { source medium campaign term content }, country, device, browser, os, properties, revenue, currency, contentId, label, visit, person |
properties is JSON, which holds the details sent with the event and cannot be selected into. No visitor is identified, as in REST. person is set only with known users switched on.
Example
Every booking in October, a page at a time:
query Bookings($site: String!, $cursor: String) { events(site: $site, name: "Booked", from: "2026-10-01", to: "2026-11-01", limit: 500, cursor: $cursor) { items { at path channel country revenue currency properties } next }}{ "site": "SITE_KEY" }Ask again with cursor set to next until it is null.
Errors
Errors come in errors, each with a message, a code under extensions, and the path and locations of the field that failed when there is one. The codes are the same as in REST:
{ "data": null, "errors": [ { "message": "There is no site with that key in your organisation", "locations": [{ "line": 1, "column": 3 }], "path": ["events"], "extensions": { "code": "unknown_site" } } ]}400query, variables that are not a JSON object, a query that does not parse or does not match the schema (bad_request), one over the limits (query_too_deep, query_too_complex), a date or type that isn't valid (invalid), or a cursor this API didn't give out (invalid_cursor)401unauthorized)403wrong_scope), or your plan doesn't include the API (not_in_plan)404unknown_site)413query_too_large)429rate_limited). Wait a minute.500internal). Try again.200| Status | When |
|---|---|
400 | No query, variables that are not a JSON object, a query that does not parse or does not match the schema (bad_request), one over the limits (query_too_deep, query_too_complex), a date or type that isn't valid (invalid), or a cursor this API didn't give out (invalid_cursor) |
401 | The key is missing, wrong or removed (unauthorized) |
403 | The key may only send events (wrong_scope), or your plan doesn't include the API (not_in_plan) |
404 | No site with that key in your organisation (unknown_site) |
413 | A query longer than 20,000 characters (query_too_large) |
429 | Too many requests (rate_limited). Wait a minute. |
500 | Something failed on our side (internal). Try again. |
200 | Success |
Limits
- Each key may make 60 GraphQL requests a minute, counted apart from the raw event API's 60.
- A query may be at most 20,000 characters long, nest at most 10 levels and ask for at most 300 fields. Each alias and each use of a fragment counts.
- Introspection is on and needs no key, so GraphQL tools can read the schema from the address. Introspection fields do not count towards the limits.
- Every answer has
Cache-Control: no-store. The newest events can take a few seconds to show up.
Was this page helpful?