API Documentation

Public Document Documentation

Complete reference for the PVTLNK REST API

Related Document: Encryption Architecture Guide

Learn about our security architecture

PVTLNK API Documentation

Overview

PVTLNK provides a RESTful API for Enterprise subscribers to programmatically manage links, access analytics, and integrate with external systems.

Base URL: https://pvtlnk.com/api

Authentication: API Token (Enterprise only)


Authentication

All /api/v1/ endpoints require an API token. Include it in the Authorization header:

Authorization: Bearer YOUR_API_TOKEN

Generate API Token

Generate a token from API Settings in your dashboard, or programmatically:

http POST /api/v1/tokens/generate Authorization: Bearer EXISTING_TOKEN

Response: json { "success": true, "api_token": "abc123...", "expires_at": "2027-03-12T00:00:00Z", "message": "API token generated successfully. Store this token securely - it will not be shown again." }

Revoke API Token

http DELETE /api/v1/tokens/revoke Authorization: Bearer YOUR_API_TOKEN

Response: json { "success": true, "message": "API token revoked successfully" }


http GET /api/v1/links Authorization: Bearer YOUR_API_TOKEN

Query Parameters: - page (integer) — Page number (default: 1) - per_page (integer) — Results per page (default: 50, max: 100) - active (boolean) — Filter to active links only (true) - archived (boolean) — Filter to archived links only (true) - scheduled (boolean) — Filter to scheduled links only (true) - collection_id (integer) — Filter by link collection

Response: json { "success": true, "links": [ { "id": 123, "short_code": "abc123", "short_url": "https://pvtlnk.com/abc123", "destination_url": "https://example.com", "title": "My Link", "description": null, "tags": [], "click_count": 150, "active": true, "archived": false, "password_protected": false, "is_scheduled": false, "scheduled_start_at": null, "scheduled_end_at": null, "expires": false, "expires_at": null, "last_clicked_at": "2026-03-10T14:22:00Z", "created_at": "2026-01-15T10:30:00Z", "updated_at": "2026-03-10T14:22:00Z", "utm_params": {} } ], "pagination": { "page": 1, "per_page": 50, "total": 123, "total_pages": 3 } }

http GET /api/v1/links/:id Authorization: Bearer YOUR_API_TOKEN

Response: Same as a single link object above, plus an analytics field: json { "success": true, "link": { "id": 123, "...": "...", "analytics": { "total_clicks": 150, "recent_clicks": [ { "clicked_at": "2026-03-10T14:22:00Z", "hashed_ip": "a3f2...", "hashed_user_agent": "b91c...", "referrer_domain": "google.com", "country_code": "CH", "device_type": "desktop", "browser_family": "Chrome", "os_family": "macOS" } ] } } }

```http POST /api/v1/links Authorization: Bearer YOUR_API_TOKEN Content-Type: application/json

{ “link”: { “destination_url”: “https://example.com”, “title”: “My Campaign Link”, “short_code”: “custom-code”, “scheduled_start_at”: “2026-04-01T00:00:00Z”, “scheduled_end_at”: “2026-04-30T23:59:59Z”, “expires”: true, “expires_at”: “2026-05-01T00:00:00Z”, “utm_params”: { “utm_source”: “newsletter”, “utm_medium”: “email”, “utm_campaign”: “apr-2026” } }, “password”: “optional-password” } ```

Note: password is a top-level parameter, not nested inside link. Scheduling (scheduled_start_at / scheduled_end_at) requires Pro or Enterprise plan.

Response: json { "success": true, "link": { "id": 456, "short_code": "custom-code", "..." : "..." } }

```http PATCH /api/v1/links/:id Authorization: Bearer YOUR_API_TOKEN Content-Type: application/json

{ “link”: { “destination_url”: “https://new-destination.com”, “title”: “Updated Title” } } ```

Response: json { "success": true, "link": { "..." : "..." } }

http DELETE /api/v1/links/:id Authorization: Bearer YOUR_API_TOKEN

Links are archived, not permanently deleted.

Response: json { "success": true, "message": "Link archived successfully" }

http GET /api/v1/links/export Authorization: Bearer YOUR_API_TOKEN

Query Parameters: - active (boolean) — Filter to active links only (true) - archived (boolean) — Filter to archived links only (true) - collection_id (integer) — Filter by link collection - created_after (ISO 8601) — Links created after this date - created_before (ISO 8601) — Links created before this date

Response: CSV file download (pvtlnk-links-YYYY-MM-DD.csv)

CSV columns: short_url, destination_url, short_code, title, description, tags, click_count, active, archived, password_protected, expires_at, created_at


```http POST /api/v1/links/bulk_import Authorization: Bearer YOUR_API_TOKEN Content-Type: application/json

{ “csv”: “destination_url,title,short_code,tags\nhttps://example1.com,Link 1,link1,\nhttps://example2.com,Link 2,link2,” } ```

Requires Enterprise plan. Send CSV as a string in the csv field.

CSV columns: destination_url (required), title, short_code, description, tags, active, password_protected, password, scheduled_start_at, scheduled_end_at, expires_at

Response: json { "success": true, "created": 2, "links": [ { "..." : "..." } ], "errors": [], "summary": { "total_rows": 2, "created": 2, "failed": 0 } }


A/B Testing (Variants) API

Requires Pro or Enterprise plan.

List Variants

http GET /api/v1/links/:link_id/variants Authorization: Bearer YOUR_API_TOKEN

Response: json { "success": true, "ab_testing_active": true, "variants": [ { "id": 1, "name": "Variant A", "destination_url": "https://landing-a.com", "weight": 50, "active": true, "click_count": 823, "click_percentage": 51.2, "created_at": "2026-01-15T10:30:00Z", "updated_at": "2026-01-15T10:30:00Z" }, { "id": 2, "name": "Variant B", "destination_url": "https://landing-b.com", "weight": 50, "active": true, "click_count": 785, "click_percentage": 48.8, "created_at": "2026-01-15T10:30:00Z", "updated_at": "2026-01-15T10:30:00Z" } ] }

Create Variant

```http POST /api/v1/links/:link_id/variants Authorization: Bearer YOUR_API_TOKEN Content-Type: application/json

{ “variant”: { “name”: “Variant B”, “destination_url”: “https://landing-b.com”, “weight”: 30 } } ```

Response: json { "success": true, "variant": { "id": 2, "name": "Variant B", "..." : "..." } }

Update Variant

```http PATCH /api/v1/links/:link_id/variants/:variant_id Authorization: Bearer YOUR_API_TOKEN Content-Type: application/json

{ “variant”: { “weight”: 70, “active”: true } } ```

Response: json { "success": true, "variant": { "..." : "..." } }

Delete Variant

http DELETE /api/v1/links/:link_id/variants/:variant_id Authorization: Bearer YOUR_API_TOKEN

Response: json { "success": true, "message": "Variant deleted successfully" }

Traffic distribution: Weights are relative, not required to sum to 100. A link with variants at weight 70 and 30 will split traffic 70/30. Traffic always goes to at least one destination — if all variants are inactive, the link’s primary destination_url is used.


Analytics API

Overview

http GET /api/v1/analytics/overview Authorization: Bearer YOUR_API_TOKEN

Returns aggregated analytics across all your links for your configured attribution window.

Response: json { "success": true, "overview": { "attribution_window_days": 30, "period": { "start_date": "2026-02-10T00:00:00Z", "end_date": "2026-03-12T00:00:00Z" }, "metrics": { "total_clicks": 15234, "unique_visitors": 12456, "average_clicks_per_visitor": 1.22 }, "top_links": [ { "id": 123, "short_code": "abc123", "title": "Popular Link", "destination_url": "https://example.com", "clicks": 2345, "last_clicked_at": "2026-03-11T18:00:00Z" } ], "device_breakdown": { "desktop": 6234, "mobile": 8123, "tablet": 877 }, "country_breakdown": { "US": 5234, "CH": 3123, "DE": 2877 }, "referrer_breakdown": { "google.com": 3234, "twitter.com": 2123 }, "time_series": { "2026-03-10": 523, "2026-03-11": 612 } } }

The attribution window is set in API Settings. It cannot be overridden per request.

Advanced Analytics

http GET /api/v1/analytics/advanced Authorization: Bearer YOUR_API_TOKEN

Response: json { "success": true, "advanced": { "attribution_window_days": 30, "period": { "start_date": "2026-02-10T00:00:00Z", "end_date": "2026-03-12T00:00:00Z" }, "conversion_analysis": [ { "link_id": 123, "short_code": "abc123", "title": "My Link", "clicks": 2345, "created_at": "2026-01-15T10:30:00Z", "conversion_rate": 12.5 } ], "browser_breakdown": { "Chrome": 8234, "Safari": 4123, "Firefox": 2877 }, "os_breakdown": { "macOS": 5234, "Windows": 4123, "iOS": 3877 }, "hourly_distribution": { "0": 120, "1": 95, "..": "..", "23": 310 }, "day_of_week_distribution": { "0": 1200, "1": 2300, "..": "..", "6": 980 }, "insights": { "peak_hour": 14, "peak_day": 2, "top_browser": "Chrome", "top_os": "macOS" }, "day_names": { "0": "Sunday", "1": "Monday", "2": "Tuesday", "3": "Wednesday", "4": "Thursday", "5": "Friday", "6": "Saturday" } } }


Conversion Tracking API

Track Conversion (Postback)

Public endpoint — no authentication required. Uses a per-goal postback token.

```http POST /api/conversions/track/:token Content-Type: application/json

{ “value”: 99.99, “currency”: “USD”, “order_id”: “ORDER-12345”, “click_id”: 789, “ip”: “203.0.113.1”, “ua”: “Mozilla/5.0 …” } ```

URL Parameters: - token — Postback token from your conversion goal

Body Parameters (all optional): - value (decimal) — Conversion value (overrides goal default) - currency (string) — 3-letter currency code - order_id (string) — External order/transaction ID (used for deduplication) - click_id (integer) — ClickAnalytic ID for explicit attribution - ip (string) — Client IP for automatic attribution (falls back to request IP) - ua (string) — User agent for automatic attribution

Response: json { "success": true, "conversion_id": 456, "attributed": true, "value": 99.99, "time_to_convert": "2.5 hours" }

Error Responses: - 404 — Invalid postback token - 410 — Conversion goal is inactive - 403 — Feature not available for subscription tier - 409 — Duplicate conversion (same order_id already tracked)


Outbound Webhooks

Enterprise users can configure a webhook URL to receive real-time event notifications.

Configuration

Set your Webhook URL and optional Webhook Secret in API Settings.

Event Types

Event Trigger
link.created A short link is created
link.updated A short link is updated
link.clicked A short link is clicked
conversion.tracked A conversion is recorded
subscription.updated Subscription status changes

Webhook Payload

json { "id": "a3f2b91c4d8e...", "event": "link.clicked", "timestamp": "2026-03-12T12:30:45Z", "version": "1.0", "data": { "link": { "id": 123, "short_code": "abc123", "title": "My Link" }, "click": { "id": 789, "country_code": "CH", "device_type": "mobile", "browser_family": "Safari", "os_family": "iOS", "referrer_domain": "twitter.com", "clicked_at": "2026-03-12T12:30:45Z" }, "variant": null } }

Request Headers

Header Value
X-PVTLNK-Event Event type (e.g. link.clicked)
X-PVTLNK-Delivery Unique delivery ID
X-PVTLNK-Signature sha256=<hmac_hex> (present if webhook secret is set)

Signature Verification

If you have set a webhook secret, verify the X-PVTLNK-Signature header:

ruby expected = OpenSSL::HMAC.hexdigest("SHA256", webhook_secret, request.raw_post) provided = request.headers["X-PVTLNK-Signature"].sub("sha256=", "") Rack::Utils.secure_compare(expected, provided)

Retry Policy

Failed deliveries are retried up to 3 times: - Attempt 1 — immediate - Attempt 2 — after 1 second - Attempt 3 — after 5 seconds - Final retry — after 30 seconds


Error Responses

All errors follow a consistent format:

json { "success": false, "error": "Human-readable error message" }

Validation errors include an additional errors array:

json { "success": false, "error": "Validation failed", "errors": ["Destination url can't be blank", "Short code has already been taken"] }

HTTP Status Codes

Code Meaning
200 Success
201 Created
400 Bad Request
401 Unauthorized (missing or invalid token)
403 Forbidden (feature not available on your plan)
404 Not Found
409 Conflict (duplicate)
410 Gone (resource deactivated)
422 Unprocessable Entity (validation failed)
500 Internal Server Error

Support

For API support, contact us through your Enterprise dashboard or at api@pvtlnk.com.