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:
Bearertoken. - Key location: Account Settings → API Access.
- Read-only keys (
tly-ro-...) can call allGETendpoints. - Full-access keys (
tly-fa-...) are required for:POST /sites/:id/hitsPOST /sites/:id/hits/batchPOST /sites/:id/eventsPOST /sites/:id/events/batchPOST /sites/:id/kudosDELETE /sites/:id/kudos/:kudo_uidPOST /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/insightsGET /sites/:id/signalsPOST /sites/:id/signalsGET /sites/:id/uptimeGET /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
730days. - Date boundaries for analytics endpoints default to
UTC. - Optional timezone mode:
time_zone=utc(default) uses UTC day boundaries.time_zone=useruses your account timezone day boundaries.
- Pagination:
pagedefault varies by endpointper_pagemax1000(hits,kudos,leaderboard),50(user_journeys,insights),100(signals,uptime)
- Hits filtering:
countryexact matchpathexact matchreferrerpartial match
- Kudos filtering:
pathexact match
- Grouped hits:
grouped=truegroup_byone ofpath,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 countrymust be 2-letter uppercase when provided (example:US,PL,XX)- Country resolution order: provided
country→ local lookup fromip_address→ IPinfocountry_codeAPI fallback →XX pathis 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 fromip_address→ IPinfocountry_codeAPI 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 eventmust usecategory.actionformatcountrymust be 2-letter uppercase when provided (example:US,PL,XX)- Country resolution order: provided
country→ local lookup fromip_address→ IPinfocountry_codeAPI fallback →XX pathis 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 fromip_address→ IPinfocountry_codeAPI 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 pathis 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, andrecommendations: 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
GET /sitesGET /sites/:id/hits?grouped=true&group_by=pathGET /sites/:id/leaderboard
Add server-side tracking
- Create full-access key
POST /sites/:id/hitsfrom your backendPOST /sites/:id/eventsfor backend interaction trackingPOST /sites/:id/kudoswhen users react- Verify ingestion with
GET /sites/:id/hitsand your site’s Events/Kudos dashboard views
Monitor health in one poll cycle
GET /sites/:id/uptimeGET /sites/:id/content- Alert from
summary/statsfields
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].