REST API v1

API Documentation

Build custom dashboards, automate reporting, or integrate analytics into your workflow.

Bearer Auth 1,000+ req/day JSON

Authentication

All API requests require authentication using a bearer token. You can create and manage API tokens from your dashboard.

Authorization Header
Authorization: Bearer pk_live_a1b2c3d4e5f6...

Token Format

Tokens follow the format pk_live_ followed by 32 random characters:

Token Structure
pk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
└──┬──┘ └┬─┘ └─────────────┬─────────────┘
 prefix  env     random (32 chars)

Token Scopes

Tokens can be created with specific permissions:

ScopeDescription
analytics:readRead install analytics for your packages
packages:readList and view package details
packages:writeCreate, update, and delete packages

Rate Limits

Currently free for everyone! While we're building out pkglnk, all users get Pro-level API limits. No restrictions, no catch.

Your current rate limit:

PlanRequests / Day
All Users (Free during development)1,000

Rate limit information is included in response headers:

Rate Limit Headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 950
X-RateLimit-Reset: 1704931200

Tracking Endpoint Limits

The /track/ proxy endpoint has separate rate limiting to prevent abuse and ensure reliable analytics for all users:

LimitValue
Max requests per IP100 per hour

When the limit is exceeded, analytics logging is paused but Git operations continue to work normally. This ensures package installs are never blocked, while protecting the analytics database from flooding attacks.

A typical package install generates 2-3 analytics events (clone_start, fetch_objects, and optionally LFS requests). The 100/hour limit allows approximately 30-50 normal installs per hour from a single IP.

URL Extensions

pkglnk supports URL extensions for monorepo packages and version pinning. These allow you to specify a subfolder path within a repository or pin to a specific branch or tag.

Subfolder Path

For monorepo packages where the Unity package lives in a subfolder, use the ?path= parameter:

Path Parameter
https://github.com/owner/repo.git?path=Packages/com.example.package

This maps to the git_path field in package objects.

Branch / Tag Reference

To pin to a specific branch or tag, use the # fragment:

Git Reference
# Pin to a specific tag
https://github.com/owner/repo.git#v1.0.0

# Pin to a branch
https://github.com/owner/repo.git#main

# Combined: path + ref
https://github.com/owner/repo.git?path=Packages/MyPackage#v2.0.0

This maps to the git_ref field in package objects.

API Response Fields

When you create or retrieve packages, the URL extensions are returned as separate fields:

FieldDescriptionExample
git_pathSubfolder path within the repositoryPackages/com.example.package
git_refGit branch or tag referencev1.0.0 or main

Endpoints

Base URL: https://pkglnk.com/api/v1

GET /packages

List all packages owned by the authenticated user.

Required scope: packages:read

Response

200 OK
{
  "packages": [
    {
      "slug": "my-package",
      "display_name": "My Package",
      "git_platform": "github",
      "git_owner": "username",
      "git_repo": "repo-name",
      "git_path": null,
      "git_ref": "v1.0.0",
      "is_private": false,
      "total_installs": 1234,
      "created_at": "2024-06-15T10:30:00Z"
    }
  ],
  "total": 1
}
GET /packages/:slug

Get details for a specific package.

Required scope: packages:read

Response

200 OK
{
  "slug": "my-package",
  "display_name": "My Package",
  "description": "A useful Unity package",
  "git_platform": "github",
  "git_owner": "username",
  "git_repo": "repo-name",
  "git_path": "Packages/com.example.package",
  "git_ref": "main",
  "is_private": false,
  "total_installs": 1234,
  "unique_ips": 890,
  "created_at": "2024-06-15T10:30:00Z",
  "updated_at": "2024-12-01T15:45:00Z"
}
GET /packages/:slug/analytics

Get install analytics for a specific package.

Required scope: analytics:read

Query Parameters

ParameterTypeDefaultDescription
daysinteger30Number of days to include (1-365)

Example Request

cURL
curl -X GET "https://pkglnk.com/api/v1/packages/my-package/analytics?days=30" \
  -H "Authorization: Bearer pk_live_a1b2c3d4..."

Response

200 OK
{
  "package": {
    "slug": "my-package",
    "display_name": "My Package"
  },
  "period": {
    "days": 30,
    "start": "2024-12-08T00:00:00Z",
    "end": "2025-01-07T23:59:59Z"
  },
  "totals": {
    "installs": 1234,
    "unique_ips": 890
  },
  "daily": [
    { "date": "2024-12-08", "installs": 45 },
    { "date": "2024-12-09", "installs": 52 }
  ],
  "by_os": {
    "windows": 612,
    "macos": 489,
    "linux": 133
  },
  "by_country": {
    "US": 456,
    "DE": 234,
    "GB": 189
  }
}
GET /analytics/summary

Get aggregated analytics across all your packages.

Required scope: analytics:read

Response

200 OK
{
  "total_packages": 5,
  "total_installs": 12450,
  "total_unique_ips": 8920,
  "top_packages": [
    { "slug": "popular-pkg", "installs": 5000 },
    { "slug": "another-pkg", "installs": 3200 }
  ]
}
GET /packages/:slug/export

Export detailed install analytics data in JSON or CSV format for offline analysis or backup.

Required scope: analytics:read

Query Parameters

ParameterTypeDefaultDescription
formatstringjsonExport format: json or csv
daysinteger30Number of days to include (1-365)

Example Request

cURL
curl -X GET "https://pkglnk.com/api/v1/packages/my-package/export?format=json&days=30" \
  -H "Authorization: Bearer pk_live_a1b2c3d4..."

JSON Response

200 OK
{
  "package": {
    "slug": "my-package",
    "display_name": "My Package"
  },
  "export_info": {
    "format": "json",
    "days": 30,
    "start_date": "2024-12-08T00:00:00.000Z",
    "end_date": "2025-01-07T23:59:59.000Z",
    "total_records": 156,
    "exported_at": "2025-01-07T12:30:00.000Z"
  },
  "installs": [
    {
      "id": "abc123",
      "timestamp": "2025-01-07T10:15:30.000Z",
      "country_code": "US",
      "city": "San Francisco",
      "os": "windows",
      "git_version": "2.43.0",
      "event_type": "clone_start",
      "subpath": null,
      "session_id": "sess_xyz789"
    }
  ]
}

CSV Response

When format=csv, returns a downloadable CSV file with headers:

CSV Headers
id,timestamp,country_code,city,os,git_version,event_type,subpath,session_id

Error Handling

The API uses standard HTTP status codes and returns errors in a consistent format:

Error Response
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid or expired API token"
  }
}

Status Codes

CodeDescription
200Success
400Bad request - invalid parameters
401Unauthorized - missing or invalid token
403Forbidden - insufficient scope
404Not found - package doesn't exist
429Rate limit exceeded
500Internal server error

README Badges

Display live install counts in your README or website. Badges are publicly accessible and don't require authentication.

Markdown
![Installs](https://pkglnk.com/badge/your-package.svg)
HTML
<img src="https://pkglnk.com/badge/your-package.svg" alt="Install count" />

Badge Styles

Choose a badge style using the ?style= parameter:

pkglnk
/badge/pkg.svg
pkglnk | 1.2k installs
upm
/badge/pkg.svg?style=upm
UPM | 1.2k installs
upm-installs
/badge/pkg.svg?style=upm-installs
UPM installs | 1.2k
installs
/badge/pkg.svg?style=installs
installs | 1.2k

Badges are cached for 1 hour to reduce load. Private packages display a "private" badge instead of the install count.