Penny Metrics
API Reference

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
Looking for the tracking script?
To collect pageviews and events from a website, use stats.js. The API documented here is for account management and reading analytics data.
Authentication

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.

Errors

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.
Sign in
POST
/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…"
}
POST
/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"
    }'
POST
/logout
Auth required

Revoke the token used to make this request.

GET
/user
Auth required

Return the authenticated user profile.

User
PUT
/user/profile
Auth required

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.
PUT
/user/notifications
Auth required

Update notification preferences.

Field
Required
Description
weekly_digest Yes Whether to receive the weekly analytics email.
Sites

Site endpoints require an active trial or subscription.

GET
/sites
Auth required

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]
            }
        }
    ]
}
POST
/sites
Auth required

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"}'
GET
/sites/{id}
Auth required

Return a single site. The {id} path parameter is the site UUID.

DELETE
/sites/{id}
Auth required

Permanently delete a site and all of its analytics data.

json

{
    "message": "Site deleted."
}
Webhooks

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.

GET
/sites/{id}/webhooks
Auth required

List all webhooks configured for a site.

POST
/sites/{id}/webhooks
Auth required

Create a webhook endpoint.

json

{
    "name": "Zapier",
    "url": "https://hooks.example.com/events",
    "secret": "optional-signing-secret",
    "pageviews": false,
    "enabled": true
}
PUT
/sites/{id}/webhooks/{webhook_id}
Auth required

Update a webhook. Omit fields you do not want to change.

DELETE
/sites/{id}/webhooks/{webhook_id}
Auth required

Remove a webhook endpoint.

POST
/sites/{id}/webhooks/{webhook_id}/test
Auth required

Queue a sample conversion payload to verify a specific endpoint.

Analytics
GET
/sites/{id}/analytics
Auth required

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}"
Supported filter properties
type, name, path, referrer, referrer_hostname, utm_source, utm_medium, utm_campaign, utm_term, utm_content, browser, os, device, country

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>"
    }
}
Event ingest

Pageviews and custom events are collected through public endpoints used by stats.js. They do not require authentication.

GET
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.

POST
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.