API Reference
This is the REST API that powers the Penny Metrics mobile app. You can use the same endpoints to build your own dashboards, automations, or integrations.
All authenticated endpoints live under a versioned base URL and return JSON.
text
https://pennymetrics.dev/api/v1
Exchange email and password for a Sanctum API token via POST /login. Send the token on every subsequent request:
bash
Authorization: Bearer {your-token}
Tokens are scoped to the user who created them. Logging out deletes the current token. Two-factor authentication is supported — see the sign-in endpoints below.
Login and two-factor challenge endpoints are rate limited to 5 attempts per minute.
Failed requests return a JSON body with a message. Validation errors include an errors object keyed by field name.
|
Status
|
Meaning
|
|---|---|
401
|
Missing or invalid token. |
402
|
Active subscription or trial required. |
403
|
Authenticated, but not allowed to access this resource. |
422
|
Validation failed. |
429
|
Too many requests. |
/login
Authenticate with email and password. Returns a bearer token, or a two-factor challenge when 2FA is enabled.
|
Field
|
Required
|
Description
|
|---|---|---|
email
|
Yes | Account email address. |
password
|
Yes | Account password. |
device_name
|
No | Label stored with the token (defaults to "mobile"). |
bash
curl -X POST https://pennymetrics.dev/api/v1/login \
-H "Content-Type: application/json" \
-d '{
"email": "you@@example.com",
"password": "your-password",
"device_name": "my-integration"
}'
json
{
"token": "1|abc123...",
"data": {
"id": 1,
"name": "Jane",
"email": "you@@example.com",
"initials": "JA",
"avatar_url": "https://…",
"email_verified": true,
"weekly_digest": true,
"billing": {
"unlimited": false,
"subscribed": false,
"on_trial": false,
"trial_days_left": 0
}
}
}
When two-factor authentication is enabled:
json
{
"two_factor": true,
"challenge_token": "eyJpdiI6…"
}
/two-factor-challenge
Complete a login challenge with a TOTP code or recovery code. Challenge tokens expire after 5 minutes and can only be used once.
bash
curl -X POST https://pennymetrics.dev/api/v1/two-factor-challenge \
-H "Content-Type: application/json" \
-d '{
"challenge_token": "eyJpdiI6…",
"code": "123456",
"device_name": "my-integration"
}'
/logout
Revoke the token used to make this request.
/user
Return the authenticated user profile.
/user/profile
Update the authenticated user's name or email. Changing the email clears email verification until the new address is confirmed.
|
Field
|
Required
|
Description
|
|---|---|---|
name
|
Yes | Display name. |
email
|
Yes | Unique account email. |
/user/notifications
Update notification preferences.
|
Field
|
Required
|
Description
|
|---|---|---|
weekly_digest
|
Yes | Whether to receive the weekly analytics email. |
Site endpoints require an active trial or subscription.
/sites
List the authenticated user's sites with 30-day visitor counts and 7-day sparklines.
json
{
"data": [
{
"id": "9b3f1c2a-…",
"domain": "example.com",
"created_at": "2026-06-01T10:00:00+00:00",
"visitors_30d": 1284,
"sparkline": {
"total_7d": 312,
"daily": [32, 41, 38, 55, 49, 52, 55]
}
}
]
}
/sites
Register a new site. Domains are normalised to lowercase bare hostnames — schemes, paths, and "www." prefixes are stripped.
bash
curl -X POST https://pennymetrics.dev/api/v1/sites \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"domain": "example.com"}'
/sites/{id}
Return a single site. The {id} path parameter is the site UUID.
/sites/{id}
Permanently delete a site and all of its analytics data.
json
{
"message": "Site deleted."
}
Push custom events (conversions) to external tools in real time. Each site can have multiple webhook endpoints — manage them in the dashboard or via the API below.
Deliveries are JSON POST requests signed with HMAC-SHA256 in the Penny-Metrics-Signature header when a signing secret is set.
/sites/{id}/webhooks
List all webhooks configured for a site.
/sites/{id}/webhooks
Create a webhook endpoint.
json
{
"name": "Zapier",
"url": "https://hooks.example.com/events",
"secret": "optional-signing-secret",
"pageviews": false,
"enabled": true
}
/sites/{id}/webhooks/{webhook_id}
Update a webhook. Omit fields you do not want to change.
/sites/{id}/webhooks/{webhook_id}
Remove a webhook endpoint.
/sites/{id}/webhooks/{webhook_id}/test
Queue a sample conversion payload to verify a specific endpoint.
/sites/{id}/analytics
Return the full analytics payload for a site — the same data structure rendered in the web dashboard.
|
Query param
|
Default
|
Options
|
|---|---|---|
range
|
30d
|
today, 7d, 30d, 90d, 12m
|
metric
|
visitors
|
visitors, visits, pageviews, viewsPerVisit, bounceRate, visitDuration
|
device_tab
|
browser
|
browser, os, device
|
utm_tab
|
utm_source
|
utm_source, utm_medium, utm_campaign
|
filter[{property}]
|
None | Any supported filter property. Multiple filters are combined with AND logic. |
bash
curl "https://pennymetrics.dev/api/v1/sites/{uuid}/analytics?range=7d&metric=pageviews" \
-H "Authorization: Bearer {token}"
Filter analytics by appending filter[property]=value parameters. Values match the stored event values, so countries use ISO 3166-1 alpha-2 codes such as AF for Afghanistan.
bash
curl "https://pennymetrics.dev/api/v1/sites/{uuid}/analytics?filter[os]=Windows&filter[country]=AF" \
-H "Authorization: Bearer {token}"
The analytics object includes metric tabs, chart buckets, top pages, referrers, devices, countries, UTMs, custom events, and the tracking snippet for the site.
json
{
"site": {
"id": "9b3f1c2a-…",
"domain": "example.com",
"created_at": "2026-06-01T10:00:00+00:00",
"visitors_30d": 0,
"sparkline": { "total_7d": 0, "daily": [] }
},
"analytics": {
"ranges": { … },
"metrics": { … },
"metricTabs": {
"visitors": { "value": "3", "change": 12 }
},
"chart": { … },
"pages": [ … ],
"referrers": [ … ],
"devices": [ … ],
"countries": [ … ],
"utms": [ … ],
"customEvents": [ … ],
"snippet": "<script defer src=…></script>"
}
}
Pageviews and custom events are collected through public endpoints used by stats.js. They do not require authentication.
https://pennymetrics.dev/api/i.gif
Default delivery via image beacon. Pass a base64url-encoded JSON payload in the d query parameter. Returns a 1×1 transparent GIF.
https://pennymetrics.dev/api/collect
Alternative delivery for custom integrations. Send JSON with a text/plain content type.
See the payload reference and first-party proxy guide in the tracking documentation.