Tinylytics API

Use the Tinylytics API to read analytics, send hits, events, and kudos from your backend, and automate reporting.

1. Quick Start

Base URL

https://tinylytics.app/api/v1

Header format

Authorization: Bearer tly-ro-your-api-key
Accept: application/json

Use tly-fa-... (Full Access) for write endpoints.

Test your key in 30 seconds

curl "https://tinylytics.app/api/v1/me" \
  -H "Authorization: Bearer tly-ro-your-api-key" \
  -H "Accept: application/json"

If valid, you get your account payload with HTTP 200.

Discovery and schema

You can bootstrap clients without reading prose docs first:

curl "https://tinylytics.app/api/v1"
curl "https://tinylytics.app/api/v1/openapi.json"

2. Authentication and Access

  • Auth scheme: Bearer token.
  • Key location: Account Settings → API Access.
  • Read-only keys (tly-ro-...) can call all GET endpoints.
  • Full-access keys (tly-fa-...) are required for:
    • POST /sites/:id/hits
    • POST /sites/:id/hits/batch
    • POST /sites/:id/events
    • POST /sites/:id/events/batch
    • POST /sites/:id/kudos
    • DELETE /sites/:id/kudos/:kudo_uid
    • POST /sites/:id/signals

Access rules

  • Any account with a valid API key can use core API endpoints.
  • Premium endpoints require an active subscription:
    • GET /sites/:id/insights
    • GET /sites/:id/signals
    • POST /sites/:id/signals
    • GET /sites/:id/uptime
    • GET /sites/:id/content
  • Signals endpoints also require insights to be enabled on the site.
  • Revoked or invalid keys return 401.
  • Write endpoint with read-only key returns 403.

3. Request Conventions

  • Dates use YYYY-MM-DD.
  • Date range limit for analytics endpoints: max 730 days.
  • Date boundaries for analytics endpoints default to UTC.
  • Optional timezone mode:
    • time_zone=utc (default) uses UTC day boundaries.
    • time_zone=user uses your account timezone day boundaries.
  • Pagination:
    • page default varies by endpoint
    • per_page max 1000 (hits, kudos, leaderboard), 50 (user_journeys, insights), 100 (signals, uptime)
  • Hits filtering:
    • country exact match
    • path exact match
    • referrer partial match
  • Kudos filtering:
    • path exact match
  • Grouped hits:
    • grouped=true
    • group_by one of path, country, referrer, browser_name, platform_name, source

4. Endpoint Directory

Method Endpoint Purpose
GET / Public API discovery metadata
GET /openapi.json OpenAPI 3.1 schema for API v1
GET /me Validate API key and return account info
GET /sites List accessible sites
GET /sites/:id Get one site
GET /sites/:id/hits Raw or grouped analytics hits
POST /sites/:id/hits Create one hit Full Access
POST /sites/:id/hits/batch Create many hits in one request Full Access
POST /sites/:id/events Create one event Full Access
POST /sites/:id/events/batch Create many events in one request Full Access
GET /sites/:id/kudos Read kudos records
POST /sites/:id/kudos Create one kudo Full Access
DELETE /sites/:id/kudos/:kudo_uid Delete one kudo by UID Full Access
GET /sites/:id/leaderboard All-time path leaderboard
GET /sites/:id/user_journeys Visitor journey analysis
GET /sites/:id/insights AI insights for the site Subscription
GET /sites/:id/signals Owner-submitted context signals Subscription
POST /sites/:id/signals Submit one context signal Full Access Subscription
GET /sites/:id/uptime Uptime + SSL/domain status Subscription
GET /sites/:id/content Content monitoring status and issues Subscription

5. Endpoint Reference

Account and Sites

GET /me

Returns current user + current API key metadata.

Accepted properties

Property Required Description
None Yes This endpoint does not accept query or body properties.
curl "https://tinylytics.app/api/v1/me" \
  -H "Authorization: Bearer tly-ro-your-api-key"
{
  "id": 123,
  "email": "[email protected]",
  "is_subscribed": true,
  "created_at": "2025-06-01T12:00:00Z",
  "api_key": {
    "name": "CLI integration",
    "access_type": "read_only",
    "last_used_at": "2026-02-12T10:00:00Z"
  }
}

GET /sites

Lists your sites with lifetime counters.

Accepted properties

Property Required Description
None Yes This endpoint does not accept query or body properties.
curl "https://tinylytics.app/api/v1/sites" \
  -H "Authorization: Bearer tly-ro-your-api-key"
{
  "sites": [
    {
      "id": 456,
      "uid": "abc123",
      "url": "https://example.com",
      "label": "My Blog",
      "lifetime_hits": 12340,
      "lifetime_unique_hits": 8920,
      "lifetime_kudos": 87,
      "active": true,
      "public": false,
      "created_at": "2025-06-01T12:00:00Z",
      "updated_at": "2026-02-14T09:30:00Z"
    }
  ]
}

GET /sites/:id

Returns one site by numeric ID.

Accepted properties

Property Required Description
id (URL path) Yes Site numeric ID. Use the id returned from GET /sites.
curl "https://tinylytics.app/api/v1/sites/456" \
  -H "Authorization: Bearer tly-ro-your-api-key"
{
  "id": 456,
  "uid": "abc123",
  "url": "https://example.com",
  "label": "My Blog",
  "lifetime_hits": 12340,
  "lifetime_unique_hits": 8920,
  "lifetime_kudos": 87,
  "active": true,
  "public": false,
  "created_at": "2025-06-01T12:00:00Z",
  "updated_at": "2026-02-14T09:30:00Z"
}

Analytics Endpoints

GET /sites/:id/hits

Read detailed hits or grouped analytics.

Accepted properties

Property Required Description
id (URL path) Yes Site numeric ID.
start_date No Range start (YYYY-MM-DD). Defaults to 30 days ago in the selected timezone mode.
end_date No Range end (YYYY-MM-DD). Defaults to today in the selected timezone mode.
time_zone No Date-boundary mode: utc (default) or user (use account timezone).
country No Filter by exact 2-letter country code.
path No Filter by exact path (for example /pricing).
referrer No Case-insensitive partial match on referrer.
grouped No Set to true to return grouped/aggregated results.
group_by No One of path, country, referrer, browser_name, platform_name, source.
page No Page number.
per_page No Page size, max 1000.
curl "https://tinylytics.app/api/v1/sites/456/hits?grouped=true&group_by=path" \
  -H "Authorization: Bearer tly-ro-your-api-key"

Grouped by path returns views (+ unique_views when enabled). Other groupings return hit_count.

To evaluate start_date and end_date in your account timezone, add time_zone=user:

curl "https://tinylytics.app/api/v1/sites/456/hits?start_date=2026-02-13&end_date=2026-02-13&time_zone=user" \
  -H "Authorization: Bearer tly-ro-your-api-key"

Response (ungrouped)

{
  "hits": [
    {
      "id": 789,
      "url": "https://example.com/pricing",
      "path": "/pricing",
      "referrer": "https://google.com",
      "country": "US",
      "browser_name": "Safari",
      "platform_name": "macOS",
      "is_mobile": false,
      "source": "google",
      "created_at": "2026-02-13T14:22:00Z"
    }
  ],
  "pagination": {
    "current_page": 1,
    "per_page": 100,
    "total_count": 1,
    "total_pages": 1
  },
  "filters": {
    "start_date": "2026-02-13",
    "end_date": "2026-02-13",
    "time_zone": "user",
    "country": null,
    "path": null,
    "referrer": null,
    "grouped": false
  }
}

Response (grouped by path)

{
  "grouped_hits": [
    {
      "path": "/pricing",
      "views": 142,
      "unique_views": 98
    }
  ],
  "pagination": {
    "current_page": 1,
    "per_page": 100,
    "total_count": 1,
    "total_pages": 1
  },
  "filters": {
    "start_date": "2026-02-13",
    "end_date": "2026-02-13",
    "time_zone": "user",
    "country": null,
    "path": null,
    "referrer": null,
    "grouped": true,
    "group_by": "path"
  }
}

unique_views is only included when unique hit tracking is enabled for the site. Other group_by values (country, referrer, browser_name, platform_name, source) return hit_count instead of views/unique_views.


POST /sites/:id/hits Full Access

Create one hit.

Accepted properties

Property Required Description
id (URL path) Yes Site numeric ID.
path Yes Path to track. Leading slash is auto-added if missing.
country No 2-letter uppercase country code (for example US, PL, XX). If provided, this value takes precedence.
ip_address No IPv4/IPv6 address used to resolve country via local lookup first, then IPinfo Lite API as fallback when country is not provided. Raw IP is not stored in hits.
url No Full page URL. Defaults to site.url + path.
referrer No Referrer URL.
user_agent No User agent string.
visitor_id No Stable visitor identifier used for dedupe/journey grouping.
source No Source override. If missing, Tinylytics may infer from URL parameters.

Payload rules

  • Body must be a single JSON object
  • Required fields: path
  • country must be 2-letter uppercase when provided (example: US, PL, XX)
  • Country resolution order: provided country → local lookup from ip_address → IPinfo country_code API fallback → XX
  • path is normalized to begin with /
curl -X POST "https://tinylytics.app/api/v1/sites/456/hits" \
  -H "Authorization: Bearer tly-fa-your-api-key" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "path": "/pricing",
    "ip_address": "8.8.8.8",
    "visitor_id": "user-123"
  }'

Response (201 created)

{
  "status": "created",
  "hit": {
    "id": 789,
    "url": "https://example.com/pricing",
    "path": "/pricing",
    "referrer": null,
    "country": "US",
    "browser_name": null,
    "platform_name": null,
    "is_mobile": false,
    "source": null,
    "unique_hash": "a1b2c3",
    "visitor_hash": "d4e5f6",
    "created_at": "2026-02-14T10:00:00Z"
  }
}

Response (202 ignored)

{
  "status": "ignored",
  "reason": "Path matches ignore rule"
}

Response (422 error)

{
  "status": "error",
  "errors": ["Path can't be blank"]
}

POST /sites/:id/hits/batch Full Access

Create many hits in one request.

Accepted properties

Property Required Description
id (URL path) Yes Site numeric ID.
[] Yes Top-level array of hit objects.
[].path Yes Path to track. Leading slash is auto-added if missing.
[].country No 2-letter uppercase country code. If provided, this value takes precedence.
[].ip_address No IPv4/IPv6 address used to resolve country via local lookup first, then IPinfo Lite API as fallback when [].country is not provided. Raw IP is not stored in hits.
[].url No Full page URL.
[].referrer No Referrer URL.
[].user_agent No User agent string.
[].visitor_id No Stable visitor identifier used for dedupe/journey grouping.
[].source No Source override.

Payload rules

  • Body must be a top-level JSON array
  • Each row follows the same field rules as single hit creation.
  • Per row country resolution order: provided country → local lookup from ip_address → IPinfo country_code API fallback → XX
  • Batch is partial-success: one bad row does not fail the whole request.
curl -X POST "https://tinylytics.app/api/v1/sites/456/hits/batch" \
  -H "Authorization: Bearer tly-fa-your-api-key" \
  -H "Content-Type: application/json" \
  -d '[
    { "path": "/valid", "country": "PL" },
    { "path": "/from-ip", "ip_address": "8.8.8.8" },
    { "path": "/fallback-xx", "ip_address": "999.999.999.999" }
  ]'
{
  "created_count": 3,
  "ignored_count": 0,
  "error_count": 0,
  "results": [
    { "index": 0, "status": "created" },
    { "index": 1, "status": "created" },
    { "index": 2, "status": "created" }
  ]
}

POST /sites/:id/events Full Access

Create one event.

Accepted properties

Property Required Description
id (URL path) Yes Site numeric ID.
event Yes Event name in category.action format.
value No Optional event value stored as event_properties["value"].
path No Optional path context. Leading slash is auto-added if missing.
country No 2-letter uppercase country code (for example US, PL, XX). If provided, this value takes precedence.
ip_address No IPv4/IPv6 address used to resolve country via local lookup first, then IPinfo Lite API as fallback when country is not provided. Raw IP is not stored in events.
url No Full page URL. Defaults to site.url + path when path is provided.
referrer No Referrer URL.
user_agent No User agent string.
visitor_id No Stable visitor identifier used for event identity/grouping hashes.
source No Source override. If missing, Tinylytics may infer from URL parameters.

Payload rules

  • Body must be a single JSON object
  • Required fields: event
  • event must use category.action format
  • country must be 2-letter uppercase when provided (example: US, PL, XX)
  • Country resolution order: provided country → local lookup from ip_address → IPinfo country_code API fallback → XX
  • path is optional and normalized to begin with / when present
curl -X POST "https://tinylytics.app/api/v1/sites/456/events" \
  -H "Authorization: Bearer tly-fa-your-api-key" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "event": "signup.started",
    "value": "pricing",
    "ip_address": "8.8.8.8",
    "visitor_id": "user-123"
  }'

Response (201 created)

{
  "status": "created",
  "event": {
    "id": 790,
    "event": "signup.started",
    "value": "pricing",
    "url": null,
    "path": null,
    "referrer": null,
    "country": "US",
    "source": null,
    "unique_hash": "a1b2c3",
    "visitor_hash": "d4e5f6",
    "created_at": "2026-02-14T10:00:00Z"
  }
}

Response (202 ignored)

{
  "status": "ignored",
  "reason": "Event matched ignore rules"
}

Response (422 error)

{
  "status": "error",
  "errors": ["Event must be 'category.action' format (2+ dot-separated segments)"]
}

POST /sites/:id/events/batch Full Access

Create many events in one request.

Accepted properties

Property Required Description
id (URL path) Yes Site numeric ID.
[] Yes Top-level array of event objects.
[].event Yes Event name in category.action format.
[].value No Optional event value stored as event_properties["value"].
[].path No Optional path context. Leading slash is auto-added if missing.
[].country No 2-letter uppercase country code. If provided, this value takes precedence.
[].ip_address No IPv4/IPv6 address used to resolve country via local lookup first, then IPinfo Lite API as fallback when [].country is not provided. Raw IP is not stored in events.
[].url No Full page URL.
[].referrer No Referrer URL.
[].user_agent No User agent string.
[].visitor_id No Stable visitor identifier used for event identity/grouping hashes.
[].source No Source override.

Payload rules

  • Body must be a top-level JSON array
  • Each row follows the same field rules as single event creation
  • Per row country resolution order: provided country → local lookup from ip_address → IPinfo country_code API fallback → XX
  • Batch is partial-success: one bad row does not fail the whole request
curl -X POST "https://tinylytics.app/api/v1/sites/456/events/batch" \
  -H "Authorization: Bearer tly-fa-your-api-key" \
  -H "Content-Type: application/json" \
  -d '[
    { "event": "signup.started", "value": "pricing", "country": "PL" },
    { "event": "signup.completed", "ip_address": "8.8.8.8" },
    { "event": "signup.cancelled", "ip_address": "999.999.999.999" }
  ]'
{
  "created_count": 3,
  "ignored_count": 0,
  "error_count": 0,
  "results": [
    { "index": 0, "status": "created" },
    { "index": 1, "status": "created" },
    { "index": 2, "status": "created" }
  ]
}

GET /sites/:id/kudos

Read detailed Kudos activity.

Accepted properties

Property Required Description
id (URL path) Yes Site numeric ID.
start_date No Range start (YYYY-MM-DD). Defaults to 30 days ago in the selected timezone mode.
end_date No Range end (YYYY-MM-DD). Defaults to today in the selected timezone mode.
time_zone No Date-boundary mode: utc (default) or user (use account timezone).
path No Filter by exact path (for example /pricing).
uid No Filter by exact kudo UID.
page No Page number.
per_page No Page size, max 1000.
curl "https://tinylytics.app/api/v1/sites/456/kudos?start_date=2026-02-01&end_date=2026-02-14" \
  -H "Authorization: Bearer tly-ro-your-api-key"
{
  "kudos": [
    {
      "id": 321,
      "uid": "pricing-kudo-1",
      "path": "/pricing",
      "created_at": "2026-02-10T08:15:00Z"
    }
  ],
  "pagination": {
    "current_page": 1,
    "per_page": 100,
    "total_count": 1,
    "total_pages": 1
  },
  "filters": {
    "start_date": "2026-02-01",
    "end_date": "2026-02-14",
    "time_zone": "utc",
    "path": null,
    "uid": null
  }
}

POST /sites/:id/kudos Full Access

Create one kudo.

Accepted properties

Property Required Description
id (URL path) Yes Site numeric ID.
path Yes Path to track. Leading slash is auto-added if missing.
custom_uid No Custom identifier for the kudo. If omitted, Tinylytics generates one.

Payload rules

  • Body must be a single JSON object
  • Required fields: path
  • path is normalized to begin with /
curl -X POST "https://tinylytics.app/api/v1/sites/456/kudos" \
  -H "Authorization: Bearer tly-fa-your-api-key" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "path": "/pricing",
    "custom_uid": "pricing-kudo-1"
  }'

Response (201 created)

{
  "status": "created",
  "kudo": {
    "id": 321,
    "uid": "pricing-kudo-1",
    "path": "/pricing",
    "created_at": "2026-02-14T10:00:00Z"
  }
}

Response (202 ignored)

{
  "status": "ignored",
  "reason": "Path matches ignore rule"
}

Response (422 error)

{
  "status": "error",
  "errors": ["Path can't be blank"]
}

DELETE /sites/:id/kudos/:kudo_uid Full Access

Delete one kudo by UID.

Accepted properties

Property Required Description
id (URL path) Yes Site numeric ID.
kudo_uid (URL path) Yes Kudo UID to delete.
curl -X DELETE "https://tinylytics.app/api/v1/sites/456/kudos/pricing-kudo-1" \
  -H "Authorization: Bearer tly-fa-your-api-key" \
  -H "Accept: application/json"

Response (200 deleted)

{
  "status": "deleted",
  "uid": "pricing-kudo-1"
}

Response (404 not found)

{
  "error": "Kudo not found"
}

GET /sites/:id/leaderboard

All-time path ranking with caching.

Accepted properties

Property Required Description
id (URL path) Yes Site numeric ID.
path No Case-insensitive partial filter for path text.
page No Page number.
per_page No Page size, max 1000.
curl "https://tinylytics.app/api/v1/sites/456/leaderboard?path=blog" \
  -H "Authorization: Bearer tly-ro-your-api-key"
{
  "leaderboard": [
    {
      "path": "/blog/hello-world",
      "total_hits": 540,
      "unique_hits": 320,
      "percentage": 12.5
    }
  ],
  "site": {
    "id": 456,
    "uid": "abc123",
    "url": "https://example.com",
    "label": "My Blog"
  },
  "pagination": {
    "current_page": 1,
    "per_page": 100,
    "total_count": 1,
    "total_pages": 1
  },
  "cache_info": {
    "cached_at": "2026-02-14T09:00:00Z",
    "expires_at": "2026-02-14T10:00:00Z"
  },
  "filters": {
    "path": "blog"
  }
}

GET /sites/:id/user_journeys

Session-style visitor path analysis with summary metrics.

Accepted properties

Property Required Description
id (URL path) Yes Site numeric ID.
start_date No Range start (YYYY-MM-DD). Defaults to 30 days ago in the selected timezone mode.
end_date No Range end (YYYY-MM-DD). Defaults to today in the selected timezone mode.
time_zone No Date-boundary mode: utc (default) or user (use account timezone).
page No Page number.
per_page No Page size, max 50.
curl "https://tinylytics.app/api/v1/sites/456/user_journeys?start_date=2026-01-01&end_date=2026-01-31&time_zone=user" \
  -H "Authorization: Bearer tly-ro-your-api-key"
{
  "user_journeys": [
    {
      "visitor_hash": "v1a2b3",
      "page_count": 4,
      "first_hit": "2026-01-15T10:00:00Z",
      "last_hit": "2026-01-15T10:12:00Z",
      "duration_minutes": 12,
      "pages": [
        { "path": "/" },
        { "path": "/blog" },
        { "path": "/blog/hello-world" },
        { "path": "/pricing" }
      ],
      "entry_page": "/",
      "exit_page": "/pricing",
      "session_duration": 720,
      "referrer": "https://google.com",
      "country": "DE",
      "browser": "Firefox"
    }
  ],
  "summary": {
    "total_visitors": 230,
    "multi_page_visitors": 95,
    "single_page_visitors": 135,
    "bounce_rate": 58.7
  },
  "insights": {
    "top_entry_pages": [
      { "path": "/", "visitors": 120 },
      { "path": "/blog", "visitors": 45 }
    ],
    "top_exit_pages": [
      { "path": "/pricing", "visitors": 60 },
      { "path": "/blog/hello-world", "visitors": 30 }
    ]
  },
  "pagination": {
    "current_page": 1,
    "per_page": 50,
    "total_count": 230,
    "total_pages": 5
  },
  "filters": {
    "start_date": "2026-01-01",
    "end_date": "2026-01-31",
    "time_zone": "user"
  }
}

GET /sites/:id/insights Subscription

Returns generated insights, signal snapshots, and site insight settings.

Accepted properties

Property Required Description
id (URL path) Yes Site numeric ID.
page No Page number.
per_page No Page size, max 50.
curl "https://tinylytics.app/api/v1/sites/456/insights" \
  -H "Authorization: Bearer tly-ro-your-api-key"
{
  "insights": [
    {
      "id": 42,
      "insights_for_date": "2026-02-13",
      "formatted_insights_date": "February 13, 2026",
      "generated_at": "2026-02-14T06:00:00Z",
      "summary": "Traffic was steadier than usual overall, with one blog post and a new referrer doing most of the lifting.",
      "signals": [
        {
          "type": "traffic_change",
          "headline": "Traffic is up 28% this week",
          "summary": "The site picked up 378 hits in the last 7 days, up from 296 the week before.",
          "importance_score": 64,
          "detected_at": "2026-02-14T06:00:00Z",
          "window": {
            "started_at": "2026-02-07T00:00:00Z",
            "ended_at": "2026-02-14T06:00:00Z"
          },
          "payload_excerpt": {
            "direction": "increase",
            "current_hits": 378,
            "previous_hits": 296,
            "absolute_change": 82,
            "change_percentage": 27.7
          }
        }
      ],
      "traffic_patterns": "Wednesday and Thursday were the busiest days, with evenings remaining your strongest hour.",
      "best_content": "Your recent Rails post is getting more attention than usual and is now one of the site's top pages.",
      "recommendations": "Keep an eye on the post that is breaking out, and consider sharing similar content while the momentum is still fresh.",
      "context_signals_count": 1,
      "context_signals": [
        {
          "id": 12,
          "occurred_at": "2026-02-12T09:00:00Z",
          "title": "Newsletter #42 sent",
          "description": "Featured the Rails post that is now breaking out.",
          "category": "newsletter",
          "metadata": {
            "path": "/blog/rails-post"
          }
        }
      ]
    }
  ],
  "pagination": {
    "current_page": 1,
    "per_page": 50,
    "total_count": 1,
    "total_pages": 1
  },
  "site": {
    "id": 456,
    "uid": "abc123",
    "url": "https://example.com",
    "label": "My Blog",
    "insights_enabled": true,
    "daily_insight_reports_active": true,
    "next_insight_job_scheduled_at": "2026-02-15T06:00:00Z"
  }
}

Each insight returns:

  • summary: the short AI overview of what changed most.
  • signals: stored auto-detected signal snapshots for that report, including headline, summary, score, detection time, window, and a small payload excerpt.
  • context_signals_count: how many owner-submitted signals were included when the insight was generated.
  • context_signals: the owner-submitted signals included in that insight.
  • traffic_patterns, best_content, and recommendations: the fuller AI explanation for the week.

GET /sites/:id/signals Subscription

Returns owner-submitted context signals for a site. Requires insights to be enabled.

Accepted properties

Property Required Description
id (URL path) Yes Site numeric ID.
start_date No Start date (YYYY-MM-DD). Defaults to 30 days ago.
end_date No End date (YYYY-MM-DD). Defaults to today.
category No Filter by category (newsletter, social, launch, maintenance, content, podcast, other).
page No Page number.
per_page No Page size, max 100.
curl "https://tinylytics.app/api/v1/sites/456/signals?start_date=2026-02-01&end_date=2026-02-14" \
  -H "Authorization: Bearer tly-ro-your-api-key"

POST /sites/:id/signals Full Access Subscription

Submit one owner context signal. Use this to tell Tinylytics about newsletters, launches, social posts, maintenance windows, and other events that help explain traffic changes in Insights and traffic spike analysis.

Accepted properties

Property Required Description
signal.occurred_at Yes When the event happened (ISO8601 datetime).
signal.title Yes Short headline, max 200 characters.
signal.description No Longer context, max 5000 characters.
signal.category No One of newsletter, social, launch, maintenance, content, podcast, other.
signal.metadata No Optional JSON object (for example url, path, campaign, tags).
signal.external_id No Idempotency key unique per site. Reusing it updates the existing signal.
curl -X POST "https://tinylytics.app/api/v1/sites/456/signals" \
  -H "Authorization: Bearer tly-fa-your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "signal": {
      "occurred_at": "2026-02-12T09:00:00Z",
      "title": "Newsletter #42 sent",
      "description": "Featured the Rails post that is now breaking out.",
      "category": "newsletter",
      "metadata": { "path": "/blog/rails-post" },
      "external_id": "newsletter-42"
    }
  }'

Monitoring Endpoints

GET /sites/:id/uptime Subscription

Returns uptime monitor status, SSL/domain details, and downtime history.

Accepted properties

Property Required Description
id (URL path) Yes Site numeric ID.
page No Page number for downtime records.
per_page No Page size for downtime records, max 100.
curl "https://tinylytics.app/api/v1/sites/456/uptime" \
  -H "Authorization: Bearer tly-ro-your-api-key"

If uptime is not enabled for the site, response is 404.

{
  "monitor": {
    "id": 101,
    "url": "https://example.com",
    "enabled": true,
    "is_down": false,
    "uptime": 99.95,
    "last_check_at": "2026-02-14T09:55:00Z",
    "next_check_at": "2026-02-14T10:00:00Z",
    "last_status_code": 200,
    "last_error_message": null,
    "status_description": "Up",
    "current_check_interval": 300,
    "period": "30d",
    "ssl": {
      "expires_at": "2026-08-01T00:00:00Z",
      "valid": true,
      "expiring_soon": false,
      "expired": false,
      "days_until_expiry": 168
    },
    "domain": {
      "tested_at": "2026-02-14T00:00:00Z",
      "expires_at": "2027-06-01T00:00:00Z",
      "remaining_days": 472,
      "source": "whois",
      "expired": false,
      "expiring_soon": false,
      "days_until_expiry": 472
    },
    "auto_paused": false,
    "created_at": "2025-06-01T12:00:00Z",
    "updated_at": "2026-02-14T09:55:00Z"
  },
  "downtimes": [
    {
      "id": 55,
      "error": "Connection timed out",
      "started_at": "2026-02-10T03:00:00Z",
      "ended_at": "2026-02-10T03:15:00Z",
      "duration": 900,
      "duration_in_words": "15 minutes",
      "partial": false,
      "ongoing": false
    }
  ],
  "pagination": {
    "current_page": 1,
    "per_page": 100,
    "total_count": 1,
    "total_pages": 1
  },
  "summary": {
    "total_downtimes": 3,
    "ongoing_downtimes": 0,
    "recent_downtimes_30_days": 1
  }
}

GET /sites/:id/content Subscription

Returns content monitoring status, issues, ignored issues, and stats.

Accepted properties

Property Required Description
id (URL path) Yes Site numeric ID.
curl "https://tinylytics.app/api/v1/sites/456/content" \
  -H "Authorization: Bearer tly-ro-your-api-key"
{
  "site": {
    "id": 456,
    "uid": "abc123",
    "url": "https://example.com",
    "label": "My Blog"
  },
  "monitoring_status": {
    "enabled": true,
    "root_path": "/blog",
    "last_check_at": "2026-02-14T08:00:00Z",
    "is_initial_check": false,
    "is_rechecking": false,
    "has_issues": true,
    "emails_paused": false,
    "emails_paused_until": null
  },
  "issues": {
    "broken_links": [
      {
        "id": 201,
        "url": "https://example.com/old-page",
        "status_code": 404,
        "error_message": "Not Found",
        "issue_type": "broken_link",
        "checked_at": "2026-02-14T08:00:00Z",
        "ignored": false
      }
    ],
    "mixed_content": []
  },
  "ignored_issues": [],
  "ok_links": [],
  "stats": {
    "total_checked": 48,
    "broken_links_count": 1,
    "mixed_content_count": 0,
    "ignored_count": 0,
    "ok_count": 47
  }
}

If content monitoring is disabled for the site, response is 403 with:

{
  "error": "Content monitoring is not enabled for this site",
  "content_monitoring_enabled": false
}

6. Common Flows

Build a dashboard

  1. GET /sites
  2. GET /sites/:id/hits?grouped=true&group_by=path
  3. GET /sites/:id/leaderboard

Add server-side tracking

  1. Create full-access key
  2. POST /sites/:id/hits from your backend
  3. POST /sites/:id/events for backend interaction tracking
  4. POST /sites/:id/kudos when users react
  5. Verify ingestion with GET /sites/:id/hits and your site’s Events/Kudos dashboard views

Monitor health in one poll cycle

  1. GET /sites/:id/uptime
  2. GET /sites/:id/content
  3. Alert from summary/stats fields

7. Errors and Status Codes

Status Meaning
200 Success
201 Resource created
202 Accepted but skipped (for ignored hits, events, or kudos)
400 Invalid parameter(s)
401 Missing/invalid/revoked API key
403 Premium endpoint requires subscription, write access required, or feature disabled
404 Resource not found
422 Validation or payload format error
500 Unexpected server error

Typical error payload:

{
  "error": "Invalid API key"
}

8. Rate Limits and Support

Authenticated API requests are rate limited to 1000 requests per hour per API key.

For implementation help: [email protected].