OnyxMetric

Partner API

Provision and manage OnyxMetric organizations on behalf of your own customers. Create an organization, add a site, set a plan, mint a sign-in link, read a summary, and delete everything again.

The Partner API is for platforms that embed OnyxMetric for their own customers and bill those customers themselves. A partner creates one organization per customer, attaches sites, chooses a plan, and can hand the customer a one-time sign-in link to the dashboard. No end user ever sees an OnyxMetric checkout.

Base path: /api/partner

Authentication

Every request carries a partner key:

Authorization: Bearer <partner key>

Partner keys are issued by OnyxMetric. The server stores only their SHA-256 hashes in the PARTNER_API_KEY_HASHES environment variable as comma-separated id:hash entries, for example acme:9f2c...,acme:41b7.... The id (letters, digits, - or _, up to 64 characters) is the stable identity of the partner: organizations are owned by the id, not by a key, so keys can change without losing access to them. An id may have several hashes at once. To add a key, generate a long random string, hash it, add id:hash to the variable, and give the partner the original string:

KEY=$(openssl rand -hex 32)
printf '%s' "$KEY" | shasum -a 256 | cut -d' ' -f1   # add this to PARTNER_API_KEY_HASHES

The variable is read on every request, so rotation needs no restart. A missing or unknown key returns 401.

To rotate without downtime: add a second id:hash entry for the same id, let the partner switch to the new key, then remove the old entry. Entries without an id: prefix are ignored.

Rate limits

Limits are per partner id (not per key) and per route, over a one-minute window. Over the limit returns 429.

RoutesLimit
Create organization, add site, set plan, summary60 / minute
Sign-in link, delete organization10 / minute

The counters live in Redis. If Redis is unavailable the limiter lets requests through, so these limits fail open until it recovers.

A partner can only see and change organizations it created. Any other organization id, including one that exists but belongs to someone else, returns 404.

Create an organization

POST /api/partner/organizations

{ "externalId": "your-customer-id", "name": "Acme Store", "ownerEmail": "owner@acme.com" }

Response:

{ "organizationId": "9f2c...", "created": true, "ownerStatus": "member" }

Idempotent per (partner, externalId): repeating the call returns the same organization with created: false. Organizations start on the free tier.

ownerStatus says how the owner is attached:

  • "member": the email was new, so an owner account was created for it and is already the organization's owner. Use the sign-in link to log them in.
  • "invited": the email already belongs to an existing account. That account is never attached to your organization automatically. The organization is created without an owner and a normal invitation email is sent to the address; the account holder accepts it while signed in. Until then no sign-in link can be minted for that address.

Add a site

POST /api/partner/organizations/:id/sites

{ "domain": "acme.com", "name": "Acme Store" }

Response: { "siteId": 123 }. Idempotent on domain. A domain that cannot be used returns a generic 409 {"error":"Conflict"}. A new site starts with the feature flags of the organization's current tier.

Set the plan

PUT /api/partner/organizations/:id/tier

{ "tier": "growth", "overage": false }

tier accepts any key in the table below. overage is optional; when omitted the current setting is kept. Response:

{
  "tier": "growth",
  "limits": { "sessions": 25000, "recordings": 1500, "retentionDays": 395, "replayRetentionDays": 30 },
  "overage": false
}

Tiers

TierSessions / monthRecordings / monthReplay keptData kept
free2,5000 (replay off)-30 days
starter10,00030014 days90 days
growth25,0001,50030 days395 days
pro100,0005,00090 days395 days
scale250,00020,000365 days730 days

At the cap

  • Session replay stops recording once the recordings cap is reached, unless overage is true. The free tier can never record, with or without overage: the server refuses replay data for it.
  • Analytics keep counting up to 120% of the sessions cap. Past 120% only 1 visitor in 10 is collected (the same visitors for the rest of the month, so their sessions stay whole), custom events and errors are still collected, and replay recording stops, unless overage is true.
  • Retention is enforced daily: data older than the tier's retentionDays (and recordings older than replayRetentionDays) is deleted. Moving to a tier with a longer window applies at once; moving to a shorter one takes effect 30 days later, so the data stays available (and exportable) until then. Organizations outside the partner program keep today's behavior: recordings are deleted after 30 days, and analytics data is not touched.
  • The cap is enforced from an events-per-session conversion (10 events per session) refreshed about every 30 minutes, so the cut-off is approximate. The usage block in the summary is the exact count.

POST /api/partner/organizations/:id/login-link

{ "email": "owner@acme.com" }

Response: { "url": "https://..." }. The link signs the member in once and expires after 10 minutes. It is returned to you, never emailed. Links are only minted for accounts your key created through this API (ownerStatus: "member") that belong to the organization. For any other existing account, and for platform administrators, the call returns 409 {"error":"existing_account"}: those people sign in themselves. An address with no account returns 404.

Summary

GET /api/partner/organizations/:id/summary?siteId=123&days=30

siteId defaults to the organization's first site, days to 30 (1 to 365).

{
  "visitors": 4210,
  "pageviews": 12877,
  "revenue": 8123.5,
  "currency": "USD",
  "topSources": [{ "source": "google", "visitors": 1800, "revenue": 3100 }],
  "usage": { "sessions": 9120, "sessionCap": 25000, "recordings": 410, "recordingCap": 1500 }
}

revenue sums the revenue property of purchase events. usage covers the current calendar month across all of the organization's sites, so you can show meters and bill overage.

Delete an organization

DELETE /api/partner/organizations/:id returns 204.

Removes the organization's sites, events and replays, then the organization itself. Accounts your key created that belong to no other organization are deleted with it; any other account is left untouched. Replay recordings (including stored payloads), v1 and v2 replay metadata and all analytics tables are removed table by table before the organization rows, and the call can safely be repeated if it fails part-way. Deletion in the analytics store is asynchronous and completes shortly after the call returns.

On this page