Overview
The OpenMetrica API gives you programmatic access to what the dashboard shows. Every stat you see on screen, every goal you create, every filter you save is available through a clean REST interface. Creating a site, importing history and managing API keys stay in the dashboard.
The API uses standard HTTP methods and returns JSON, except the export download, which returns a ZIP file. All endpoints live under /api/v1/, and the ones documented here require authentication via Bearer token. You can use either a JWT token (what the dashboard uses, valid for two hours, or for 72 hours when you choose to stay signed in) or an API key (designed for server-to-server integrations and scripts). Call the API from a server or a script, and keep keys out of web pages.
Base URL for all requests:
https://app.openmetrica.com/api/v1/Authentication
API keys are the recommended way to authenticate. Each key is scoped to a single site and carries only the permissions you choose. Keys are hashed with SHA-256 before storage, so even a database breach would not expose your raw key. A site's owner and editors can create keys, up to 20 active keys per site, and a key never grants more than its creator's role allows, so an editor cannot give a key site:write, shared_links:write or members:write. Each request is also checked against the creator's current role, so a key stops working when the person who created it leaves the site or deletes their account, and a demoted creator's keys lose what the new role does not allow.
Creating an API key
- Open Account Settings in the dashboard
- Go to the API Keys tab
- Select the site you want the key for
- Give the key a name (something descriptive, like 'CI Stats Fetcher')
- Pick the permissions this key needs
- Click Create and copy the key immediately. It will not be shown again.
Keys follow the format om_ followed by 64 hexadecimal characters. The first 8 characters after the prefix are stored as a visible identifier so you can tell keys apart in the dashboard.
Using the key
Pass the key as a Bearer token in the Authorization header:
Authorization: Bearer om_a1b2c3d4e5f6...Treat API keys like passwords. Do not commit them to version control. Use environment variables or a secrets manager.
Your first request
Let's fetch aggregate stats for the last 30 days. Replace YOUR_KEY with your actual API key and set the correct site_id.
curl -s \
-H "Authorization: Bearer YOUR_KEY" \
"https://app.openmetrica.com/api/v1/stats/aggregate?site_id=1&period=30d"
A successful response looks like this:
{
"visitors": { "value": 12483 },
"sessions": { "value": 18291 },
"pageviews": { "value": 34102 },
"bounce_rate": { "value": 42.3 },
"visit_duration": { "value": 184 },
"pages_per_visit": { "value": 1.86 }
}
If you get a 401, check that the key is correct and not revoked. If you get a 403, the key is missing the stats:read permission, the site_id is not the site the key belongs to, or the person who created the key is no longer a member of the site.
Permissions
Each API key carries a set of permissions that control what it can access. Permissions follow a resource:action pattern. There are nine resources with two actions each (read and write), plus a standalone stats:read, which makes 19 permissions.
Write does not imply read. If you want a key that can both read and create goals, grant goals:read and goals:write explicitly. This is intentional, so a CI/CD key that creates goals cannot read your traffic stats unless you grant that too.
| Permission | Description |
|---|---|
stats:read | Aggregate, timeseries, breakdown, realtime, conversions, funnels |
site:read | View site configuration and settings |
site:write | Update display name, timezone, bot filtering and noscript collection |
goals:read | List and view goal definitions |
goals:write | Create, update, and delete goals |
funnels:read | List and view funnel definitions |
funnels:write | Create, update, and delete funnels |
audiences:read | List saved audience filters |
audiences:write | Create, update, and delete audience filters |
custom_properties:read | List custom properties and view breakdowns |
custom_properties:write | Create, update, delete, and discover properties |
guards:read | List exclusion rules (IP, country, page, hostname) |
guards:write | Create, update, and delete exclusion rules |
shared_links:read | View public dashboard status and shared links, with each link's token only for a key the site owner created |
shared_links:write | Toggle public dashboard, manage shared links |
members:read | List site members, and pending invitations for a key the site owner created |
members:write | Change roles, remove members, create and revoke invitations |
export:read | Check export job status and download completed exports |
export:write | Start new data export jobs |
Stats endpoints
All stats endpoints require the stats:read permission. Pass site_id as a query parameter.
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1/stats/aggregate | Headline metrics, visitors, sessions, pageviews, bounce rate, duration and pages per visit |
GET | /api/v1/stats/timeseries | Time-series data points (daily or hourly intervals) |
GET | /api/v1/stats/breakdown | Break down metrics by dimension (page, entry and exit page, source, medium, channel, country, device, browser, OS, UTM tags, and for paid traffic ad_network and campaign_tags, whether the link named a campaign) |
GET | /api/v1/stats/realtime | Visitors in the last minute, plus unique visitors and pageviews over the last 60 minutes, minute by minute; counts the whole site, with bots counted as the site's bot filter says |
GET | /api/v1/stats/conversions | Conversion stats for every goal shown on the dashboard; goals are kept 12 months, and when the period reaches further, since names the first day the figures cover |
GET | /api/v1/stats/funnel | Per-step visitor counts for a given funnel, over at most 90 days; filters on a visit's dimensions apply to every step, a page filter is left out and named in ignored_filters |
Common query parameters
| Parameter | Example | Description |
|---|---|---|
site_id | 1 | Required. Your site ID. |
period | 30d | Time range. Options: realtime, day, yesterday, 24h, 7d, 28d, 30d, 91d, month, mtd, last_month, 6mo, 12mo, ytd, all, custom. Default 30d. 24h is the current hour so far and the 23 hours before it, and leaves imported history out. |
date | 2026-03-15 | For 'day' and 'custom' periods. A custom period takes two dates joined by a comma, 2026-03-01,2026-03-31, starting on 2020-01-01 or later, as all does. A range that reaches past the site's today ends today. A custom period without a range, or one that starts before 2020-01-01 or after today, returns 400 invalid_range, and so does a day period whose date is not one calendar day. |
filters | source==Google | Filter results. Format: dimension==value or dimension!=value, joined with ; and | for OR within one dimension. A value that holds | or ; writes it with a backslash before it, as \| and \;, and a backslash as \\, so a goal named Pricing | Plans is goal==Pricing \| Plans. Values match exactly as the reports list them, spaces included, and are case-sensitive, except a channel's, which matches in any case and also as Affiliate or Other, the names Affiliates and Unassigned had before. A country is its two-letter ISO code, such as US, and a device is Desktop, Mobile or Tablet. A filter on a name that is neither a dimension nor goal, a filter with no value and goal!= return 400 invalid_filter, and the whole parameter may run to 1,000 characters. An exit_page filter applies only together with a goal filter, where it keeps the conversions whose visit ended on that page; without one it returns 400 exit_page_needs_goal. |
include_bots | suspected | Every event carries a bot score from 0, a human, to 3, a confirmed bot. none leaves out events scored 2 or 3, suspected keeps those scored 2, and all keeps everything. The default is the site's bot_filter_mode, none unless it was changed. |
include_noscript | false | Set to false to count only visits where the tracker ran, excluding the noscript pixel. Default includes both. |
limit | 10 | Breakdown rows to return, 1 to 100. Default 10. A value from 101 to 200 returns 100 rows, and one outside 1 to 200 is a 400. |
property | country | Breakdown only. The dimension to break down by, see below. Default page. |
offset | 100 | Breakdown only. Rows to skip, to fetch the next page of a long list. A list ends at its 10,000th row, and an offset there or past it returns no rows. Default 0. |
search | blog | Breakdown only. Keeps the rows whose own value contains these characters, in any case, before the list is paged. Up to 200 characters. |
order_by | pageviews | Breakdown only. The column to sort by, one the dimension's rows carry (see below). Any other column sorts by the dimension's own, which is entrances for entry_page, exits for exit_page and visitors for the rest. Rows that tie are ordered by their value, ascending. |
order | asc | Breakdown only. desc or asc, for the sort column. Default desc. |
metric | pageviews | Timeseries only. One of visitors, sessions, pageviews, bounce_rate, visit_duration, pages_per_visit, or under a goal filter one of visitors, events, conversion_rate. Default visitors. |
interval | hour | Timeseries only. hour or date. Default date. hour covers at most 7 days; a longer period by the hour returns 400 interval_too_fine. |
compare | previous_period | Aggregate only. Adds the change against the previous equal period. For 24h the change compares the 24 complete hours before the current one with the 24 hours before those, while each value still includes the current hour. |
funnel_id | 3 | Funnel only, and required there. The funnel to compute. |
Breakdown dimensions
Use the property parameter to choose what to break down by. There is no namespace prefix, and a value outside this list is rejected with a 400. These seventeen are the whole set:
- page, entry_page, exit_page
- source, medium, channel
- country, device, browser, os
- utm_source, utm_medium, utm_campaign, utm_term, utm_content
- ad_network, campaign_tags, for paid traffic: the ad network a click came through (its click ID's network, Google Ads for the mediums gsn and gdn or a utm_source such as google-ads, else its source's), and whether its link named a campaign (tagged or untagged)
Geolocation stops at the country, so there is no region or city dimension, and versions of browsers and operating systems are not stored separately. The first fifteen names are what the filters parameter accepts, and goal besides, described below; ad_network and campaign_tags are breakdowns only.
A source is the visit's utm_source when its link names one, and the site it came from otherwise, as Plausible reads it: a known name or address of a source is given that source's name, so google, google.de and a visit from Google share the row Google, and a utm_source such as none, (not set) or an unfilled {placeholder} names no source. It is worked out when a report is read, so earlier visits follow too. medium is the referrer's kind (search, social, referral) and the UTM fields are as tagged.
A channel is one of Direct, Organic Search, Paid Search, AI Assistants, Organic Social, Paid Social, Organic Video, Paid Video, Organic Shopping, Paid Shopping, Email, Referral, Display, Paid Other, Affiliates, Cross-network, Audio, SMS, Mobile Push Notifications and Unassigned, the last for traffic that fits no rule; a line of imported history that came with a channel of another name keeps that name. It is worked out from the visit's recorded referrer and UTM tags each time a report is read, so a rule added later also applies to earlier visits whose recorded source it knows. A filter also takes a name in any case, and the names Affiliate and Other, which became Affiliates and Unassigned.
With a page filter and no goal filter, the aggregate endpoint also returns time_on_page, the average seconds between a view of the page and the next page of the same visit, up to an hour, over the views that had a next one (null when none had), measured from the raw event log since 21 October 2026; and a breakdown by a visit's dimension (source, channel, UTM fields, country, device, browser, operating system) gives each row time_on_page instead of visit_duration, which under a page filter would be the time spent before reaching it.
Every breakdown row carries visitors and pageviews. Entry page rows add entrances and bounce_rate. Exit page rows add exits and exit_rate, the share of the page's views that ended a visit, which is null when imported history leaves the page's views unknown. Page rows add bounce_rate, and the rows of the other dimensions add bounce_rate and visit_duration, the average length of a visit in seconds. For an exit page, visitors and pageviews are those of the page itself, so they count everyone who viewed it. An exit_page filter applies only beside a goal filter, where it keeps the conversions of visits that ended on that page, and any other request with one answers 400 exit_page_needs_goal.
Filtering by a goal
A filter of goal==Signup keeps the visitors who completed the goal named Signup, and the aggregate, timeseries and breakdown endpoints then count conversions instead of visits. goal==Signup|Purchase keeps those who completed either goal, a name matches every goal that has it, and a goal filter takes == only, so goal!= is ignored. The other filters apply to the conversion itself. Its page is the page where it happened, its source, channel and UTM fields are those of the visit it happened in, its entry and exit pages are that visit's first and last page, and its country, device, browser and operating system are those of the request that recorded it. Imported history holds no conversions and is left out. A name that no goal has counts nothing. Conversions recorded before October 21, 2026, 11:00 UTC, carry no visit, so they count in the totals and by page, while a filter or breakdown on any other dimension leaves them out.
Under a goal filter the aggregate endpoint returns visitors, the unique conversions, events, the total conversions, and conversion_rate, the visitors who converted as a percentage of the visitors who viewed a page under the other filters. Both are counted in the raw event log, which is kept for 12 months, so a longer range counts its last 12 months. When the goals the filter names that record revenue all record it in one currency, it also returns total_revenue and average_revenue, the revenue of their conversions in total and per conversion that carried one, each with that currency. Breakdown rows carry visitors, events and conversion_rate, the share of the row's own visitors who converted, which is null when no visitor of that row viewed a page, and order_by takes any of the three. Of the goals it lists, the conversions endpoint keeps those the filter names, and counts them under the other filters, as it counts every goal.
# Where the visitors who signed up came from, with each source's conversion rate
curl -s \
-H "Authorization: Bearer $OPENMETRICA_KEY" \
"https://app.openmetrica.com/api/v1/stats/breakdown?site_id=1&period=30d&property=source&filters=goal==Signup"
Goals
Goals track conversions. A pageview goal fires when a visitor views a page whose path matches the goal's page_path, exactly or with * as a wildcard, and needs no code. A custom event goal counts an event your own code sends. A scroll depth goal is a custom event under the hood, counting events named Scroll Depth that carry a depth property, and the tracker sends those for you once scroll measurement is switched on.
Scroll measurement is off until the tracking snippet carries data-scroll="true". Create a scroll depth goal and the Installation tab adds that attribute to your snippet, ready to copy again. It stays off by default because each page view then sends one extra event carrying the deepest point the visitor reached, and those count towards your monthly custom event allowance.
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /api/v1/sites/:siteId/goals | goals:read | List all goals with conditions |
GET | /api/v1/sites/:siteId/goals/:id | goals:read | Get a single goal |
POST | /api/v1/sites/:siteId/goals | goals:write | Create a goal |
PUT | /api/v1/sites/:siteId/goals/:id | goals:write | Update a goal |
DELETE | /api/v1/sites/:siteId/goals/:id | goals:write | Delete a goal |
Creating a goal
curl -s -X POST \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"display_name": "Signed Up",
"goal_type": "custom_event",
"event_name": "Signup"
}' \
"https://app.openmetrica.com/api/v1/sites/1/goals"
Funnels
Funnels let you define a sequence of 2 to 8 pageview or custom event goals and measure drop-off between each step, within a window of 1 to 30 days (window_days, default 30). You create funnels by referencing existing goal IDs, up to 20 funnels per site. A scroll depth goal used as a step matches no visitor, and deleting a goal removes it from every funnel that uses it.
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /api/v1/sites/:siteId/funnels | funnels:read | List all funnels with steps |
GET | /api/v1/sites/:siteId/funnels/:id | funnels:read | Get a single funnel |
POST | /api/v1/sites/:siteId/funnels | funnels:write | Create a funnel |
PUT | /api/v1/sites/:siteId/funnels/:id | funnels:write | Update a funnel |
DELETE | /api/v1/sites/:siteId/funnels/:id | funnels:write | Delete a funnel |
Creating a funnel
curl -s -X POST \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Signup Flow",
"goal_ids": [1, 2, 3],
"window_days": 7
}' \
"https://app.openmetrica.com/api/v1/sites/1/funnels"
Site settings
Read and update your site configuration. The read endpoint returns the domain, display name, timezone, verification status, bot filter mode, and whether noscript collection and the public dashboard are on. Only the site owner may update it, so a key needs site:write and must have been created by the owner.
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /api/v1/sites/:id | site:read | Get site details |
PATCH | /api/v1/sites/:id | site:write | Update site settings |
Updatable fields
- display_name: human-readable name shown in the dashboard
- timezone: IANA timezone (e.g. Europe/Berlin) for date grouping
- bot_filter_mode: none (default), suspected or all, the default for include_bots
- noscript_enabled: true or false (default false), counts visitors whose browser never runs the tracker
Audiences
Audiences are saved filter sets. Instead of applying the same filters every time you check stats, save them as an audience. An audience keeps its filters as a list of objects with dimension, label, operator (== or !=) and values, so to use one, write each filter as its dimension, operator and values joined with |, join the filters with ; and pass the result in the filters parameter. A site can keep up to 30 audiences.
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /api/v1/sites/:siteId/audiences | audiences:read | List saved audiences |
POST | /api/v1/sites/:siteId/audiences | audiences:write | Create an audience |
PUT | /api/v1/sites/:siteId/audiences/:id | audiences:write | Update an audience |
DELETE | /api/v1/sites/:siteId/audiences/:id | audiences:write | Delete an audience |
Custom properties
Custom properties let you attach metadata to events. For example, you can track which pricing plan a visitor is on, or which A/B test variant they saw. The discover endpoint lists the property keys your events carried in the last 30 days, with the number of events for each, and you register the ones you want listed, up to 30 per site.
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /api/v1/sites/:siteId/custom-properties | custom_properties:read | List registered properties |
POST | /api/v1/sites/:siteId/custom-properties | custom_properties:write | Register a property |
PUT | /api/v1/sites/:siteId/custom-properties/:id | custom_properties:write | Update description |
DELETE | /api/v1/sites/:siteId/custom-properties/:id | custom_properties:write | Remove a property |
GET | /api/v1/sites/:siteId/custom-properties/discover | custom_properties:write | Auto-discover property keys from event data |
GET | /api/v1/sites/:siteId/custom-properties/breakdown | custom_properties:read | Get value breakdown for a property |
The breakdown takes prop_key (required), event_name to count the values of one event only, period (default 7d, and any number of days such as 14d), date, include_bots (none, suspected or all as on the other endpoints, the site's bot_filter_mode by default; true and false still mean all and none), include_noscript, filters, offset, search, order_by (events, visitors or conversion_rate) and order. It returns up to 100 values a page, each with visitors and events, sorted by events unless you ask otherwise, and total_visitors, the visitors a value's share divides by: the site's visitors whose browser ran the tracker, on its own days, never imported history. With a goal filter in filters it lists the values the property carried on that goal's conversions, each with visitors, events and conversion_rate, the visitors who sent the value as a percentage of the visitors who viewed a page under the other filters, sorted by visitors. With any other filter the values are those of the visits the filters keep, counted from the raw event log, and total_visitors is their tracked visitors. Filtered and goal figures come from the raw event log, which keeps 12 months: when the period reaches further, since names the first day they cover. They are traffic figures, so an API key reads them, and total_visitors, only when it also holds stats:read.
Guards
Guards are exclusion rules that drop unwanted events as they arrive, before they reach your stats. You can drop events by IP address, country or page path, and you can list the hostnames your site uses, after which events from any other hostname are dropped. Rules act on new events only, and a site can have up to 30 rules of each type.
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /api/v1/sites/:siteId/guards | guards:read | List all exclusion rules |
POST | /api/v1/sites/:siteId/guards | guards:write | Create a rule |
PATCH | /api/v1/sites/:siteId/guards/:id | guards:write | Update a rule |
DELETE | /api/v1/sites/:siteId/guards/:id | guards:write | Delete a rule |
Guard types
- ip: block a single IP address
- country: block traffic from a specific country (ISO 3166-1 alpha-2)
- page: exclude a page path, where a * at the start or end matches any prefix or suffix, so /admin/* also covers /admin
- hostname: allow only the hostnames you list, once there is at least one, with * at the start or end as for pages
Visibility and shared links
Control who can see your analytics. You can toggle a public dashboard (accessible without login) and create shareable links, each with an optional password of at least 6 characters and an optional expiry of up to 730 days, up to 20 links per site. Only the site owner manages them, so changes need a key the owner created.
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /api/v1/sites/:siteId/visibility | shared_links:read | Get public dashboard status and list shared links |
PUT | /api/v1/sites/:siteId/visibility/public | shared_links:write | Toggle public dashboard on/off |
POST | /api/v1/sites/:siteId/shared-links | shared_links:write | Create a shared link |
PUT | /api/v1/sites/:siteId/shared-links/:id | shared_links:write | Update a shared link |
DELETE | /api/v1/sites/:siteId/shared-links/:id | shared_links:write | Delete a shared link |
Members and invitations
Manage your team. List members, change roles, remove people, and send invitations. OpenMetrica has three roles. The owner has full control, an editor manages goals, funnels, audiences, custom properties and guards, imports history and creates API keys, but cannot change the site's settings, members or sharing, and a viewer has read-only access. Changing members and invitations needs a key the owner created, and the owner can never be removed or demoted.
| Method | Endpoint | Permission | Description |
|---|---|---|---|
GET | /api/v1/sites/:id/members | members:read | List all members with roles |
PATCH | /api/v1/sites/:id/members/:userId/role | members:write | Change a member's role |
DELETE | /api/v1/sites/:id/members/:userId | members:write | Remove a member |
GET | /api/v1/sites/:id/invitations | members:read | List pending invitations |
POST | /api/v1/sites/:id/invitations | members:write | Create an invitation |
POST | /api/v1/sites/:id/invitations/batch | members:write | Send multiple invitations at once |
DELETE | /api/v1/sites/:id/invitations/:inviteId | members:write | Revoke a pending invitation |
Accepting and declining invitations requires a user session (JWT). This cannot be done through API keys because it needs a real user identity.
Data export
Export your analytics data as CSV files bundled in a ZIP archive, one CSV per dataset, by day in the site's timezone, a row per day for visitors and a row per day and value for the others. Exports run as background jobs, so you start a job, poll its status, and download the result when it is ready. The datasets are visitors, pages, entry_pages, exit_pages, sources, channels, utm_campaigns, countries, devices, browsers, operating_systems and imported, the last holding your imported history as it is stored. Events scored as bots (2 or 3) are always left out.
| Method | Endpoint | Permission | Description |
|---|---|---|---|
POST | /api/v1/sites/:siteId/export | export:write | Start an export job |
GET | /api/v1/sites/:siteId/export/:jobId | export:read | Check job status and progress |
GET | /api/v1/sites/:siteId/export/:jobId/download | export:read | Download the completed ZIP file |
Starting an export
curl -s -X POST \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"start_date": "2026-01-01",
"end_date": "2026-03-31",
"datasets": ["visitors", "pages", "sources"]
}' \
"https://app.openmetrica.com/api/v1/sites/1/export"
The response is a 202 with a jobId. Poll the status endpoint, which returns status (pending, processing, done or error), progress from 0 to 100 and error, until the status is 'done' or 'error', then call the download endpoint to get your ZIP file. The archive can be downloaded once, within two hours of the job finishing. A site runs at most two exports at a time, and a third gets a 429. Each dataset may hold up to 100 MB and an export up to 250 MB in all, and a larger one ends in 'error', so pick a shorter range or fewer datasets.
Rate limits
API keys are limited to 600 requests per hour, counted per site across all of its keys. The counter resets at the start of each hour, UTC. If you exceed the limit, you will get a 429 response whose error is rate_limit_exceeded, with no Retry-After header.
A site's reports are also read only so many at a time, across every client, so that one very busy dashboard cannot slow every other site down. A request that finds its site at that limit waits up to three seconds and then gets a 429 whose error is site_busy, with Retry-After: 2. The aggregate, timeseries and breakdown endpoints keep an answer for up to a minute (a breakdown asked with offset, search or order_by is not kept), so the same request asked again within it gets the same figures, and a change to the site's goals, funnels, properties, settings or imports shows at once.
| Auth method | Limit | Window |
|---|---|---|
API key | 600 requests | Per clock hour, per site |
Dashboard (JWT) | 120 requests | Per minute per user |
Public endpoints | 60 requests | Per minute per IP |
All limits are counted in Redis with atomic Lua scripts, so they hold across every API instance. While Redis cannot be reached, each instance counts on its own.
Error handling
The API uses standard HTTP status codes. Every error response from the API includes a JSON body with an error code. Most add a human-readable message, and a validation_error lists the failing fields in a fields array. Every response carries an X-Request-Id header, which helps us find your request when you contact support.
{
"error": "insufficient_permission",
"required": "goals:write"
}
| Status | Meaning |
|---|---|
400 | Bad request. The input failed validation. The fields array names what failed. |
401 | Unauthorized. Missing, invalid, or revoked API key. |
403 | Forbidden. The key is valid but lacks the required permission, belongs to another site, or its creator is no longer a member of the site or no longer holds the role the endpoint needs. |
404 | Not found. The resource does not exist or belongs to a different site. |
409 | Conflict. For example an export that is not ready to download. |
410 | Gone. The export's archive is gone, because it was already downloaded or has expired. |
422 | Unprocessable. A limit was reached, a referenced goal does not exist, or a funnel was asked for more than 90 days. |
429 | Rate limited, the site's reports are being read at their limit (site_busy, with Retry-After), or the site already runs two exports. Wait and retry later. |
500 | Server error. Something went wrong on our side. Try again or contact support. |
503 | Service unavailable. Try again shortly. |
Code examples
JavaScript (Node.js)
const API_KEY = process.env.OPENMETRICA_KEY;
const SITE_ID = 1;
async function getStats(period = '30d') {
const res = await fetch(
`https://app.openmetrica.com/api/v1/stats/aggregate?site_id=${SITE_ID}&period=${period}`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
if (!res.ok) {
const err = await res.json();
throw new Error(`${res.status}: ${err.message ?? err.error}`);
}
return res.json();
}
getStats().then((stats) => {
console.log(`Visitors: ${stats.visitors.value}`);
});
Python
import os
import requests
API_KEY = os.environ["OPENMETRICA_KEY"]
SITE_ID = 1
BASE = "https://app.openmetrica.com/api/v1"
headers = {"Authorization": f"Bearer {API_KEY}"}
# Fetch aggregate stats
r = requests.get(f"{BASE}/stats/aggregate", params={
"site_id": SITE_ID,
"period": "30d",
}, headers=headers)
r.raise_for_status()
stats = r.json()
print(f"Visitors: {stats['visitors']['value']}")
print(f"Pageviews: {stats['pageviews']['value']}")
Creating a goal and checking conversions
# Create a custom event goal
curl -s -X POST \
-H "Authorization: Bearer $OPENMETRICA_KEY" \
-H "Content-Type: application/json" \
-d '{"display_name": "Purchase", "goal_type": "custom_event", "event_name": "Purchase"}' \
"https://app.openmetrica.com/api/v1/sites/1/goals"
# Check conversions for all goals
curl -s \
-H "Authorization: Bearer $OPENMETRICA_KEY" \
"https://app.openmetrica.com/api/v1/stats/conversions?site_id=1&period=7d"
Ready to build on top of your analytics? Every plan, Free included, comes with the full API.