API Documentation
Complete reference for the LinkShort REST API. All endpoints, parameters, and examples.
Introduction
The LinkShort API lets you programmatically create and manage short links, track analytics, and receive real-time webhook notifications. All responses are JSON.
Base URL
https://api.tooldit.com/api/v1All requests must be made over HTTPS. HTTP requests will be rejected.
Authentication
The API supports two authentication methods. API Keys are recommended for server-to-server integrations.
Method 1 — API Key (Recommended)
Generate an API key from your dashboard and pass it in the Authorization header:
curl https://api.tooldit.com/api/v1/links \
-H "Authorization: sk_your_api_key_here"Method 2 — JWT Token
For dashboard integrations, use a JWT obtained from the login endpoint:
curl https://api.tooldit.com/api/v1/links \
-H "Authorization: Bearer your_jwt_token"Get your API key from your API Keys dashboard.
Rate Limits
Rate limits are applied per API key and reset every minute.
| Plan | Requests / minute |
|---|---|
| Free | 30 |
| Pro | 200 |
| Business | 1,000 |
Rate limit info is returned in response headers:
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 25
X-RateLimit-Reset: 1234567890When exceeded you will receive a 429 response:
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests"
}
}Error Handling
All errors follow a consistent format:
{
"success": false,
"data": null,
"error": {
"code": "ERROR_CODE",
"message": "Human readable message"
},
"timestamp": "2026-04-29T12:00:00Z"
}| Error Code | HTTP | Description |
|---|---|---|
| LINK_NOT_FOUND | 404 | Link does not exist |
| LINK_EXPIRED | 410 | Link has expired |
| SLUG_ALREADY_EXISTS | 409 | Custom slug is already taken |
| INVALID_URL | 400 | URL format is invalid |
| PLAN_LIMIT_EXCEEDED | 402 | Monthly link or click limit reached |
| API_KEY_INVALID | 401 | Invalid or revoked API key |
| RATE_LIMIT_EXCEEDED | 429 | Too many requests per minute |
| UNAUTHORIZED | 401 | Missing or invalid authentication |
Link Expiry Format
The expiresIn field accepts duration strings:
| Value | Description |
|---|---|
| "30m" | 30 minutes |
| "1h" | 1 hour |
| "12h" | 12 hours |
| "1d" | 1 day |
| "7d" | 7 days |
| "30d" | 30 days |
| "3mo" | 3 months |
| "1y" | 1 year |
| "10y" | 10 years |
| "never" | Never expires (Pro/Business only) |
Free plan: maximum expiry is 7 days. Values exceeding this are clamped.
Create Link
/api/v1/linksCreates a new short link.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| originalUrl | string | Required | The destination URL to shorten |
| customSlug | string | Optional | Custom short code (alphanumeric, hyphens, underscores) |
| title | string | Optional | Human-readable label for the link |
| expiresIn | string | Optional | Expiry duration — see Expiry Format |
| singleUse | boolean | Optional | Auto-disable after the first click |
| domain | string | Optional | Custom domain hostname (must be verified) |
Request example
curl -X POST https://api.tooldit.com/api/v1/links \
-H "Authorization: sk_your_key" \
-H "Content-Type: application/json" \
-d '{
"originalUrl": "https://youtube.com/watch?v=abc",
"customSlug": "my-video",
"title": "My YouTube Video",
"expiresIn": "7d",
"singleUse": false
}'Response — 200 OK
{
"success": true,
"data": {
"id": 1,
"shortCode": "my-video",
"shortUrl": "https://go.tooldit.com/my-video",
"originalUrl": "https://youtube.com/watch?v=abc",
"title": "My YouTube Video",
"status": "ACTIVE",
"singleUse": false,
"usedCount": 0,
"expiresAt": "2026-05-06T12:00:00Z",
"createdAt": "2026-04-29T12:00:00Z",
"source": "API"
}
}List Links
/api/v1/linksReturns a paginated list of your links.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| page | number | Default: 0 | Page number (zero-based) |
| size | number | Default: 20 | Items per page (max 100) |
| search | string | Optional | Search by short code, URL, or title |
| status | string | Default: ALL | Filter: ACTIVE, EXPIRED, USED, DISABLED |
Response — 200 OK
{
"success": true,
"data": {
"content": [
{
"id": 1,
"shortCode": "my-video",
"shortUrl": "https://go.tooldit.com/my-video",
"originalUrl": "https://youtube.com/watch?v=abc",
"title": "My YouTube Video",
"status": "ACTIVE",
"usedCount": 42,
"createdAt": "2026-04-29T12:00:00Z"
}
],
"page": 0,
"size": 20,
"total": 150,
"totalPages": 8
}
}Get Link
/api/v1/links/{shortCode}Returns a single link by its short code.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| shortCode | string | Required | The short code of the link |
Response — 200 OK
{
"success": true,
"data": { ...link object }
}Update Link
/api/v1/links/{shortCode}Updates an existing link. All fields are optional — only provided fields are changed.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| originalUrl | string | Optional | New destination URL |
| title | string | Optional | New title |
| expiresIn | string | Optional | New expiry duration |
| singleUse | boolean | Optional | Toggle single-use mode |
| status | string | Optional | ACTIVE or DISABLED |
Response — 200 OK
{
"success": true,
"data": { ...updated link object }
}Delete Link
/api/v1/links/{shortCode}Permanently deletes a link and all its analytics data.
Response — 200 OK
{
"success": true,
"data": null,
"message": "Link deleted successfully"
}Bulk Create Links
/api/v1/links/bulkCreates multiple links in a single request. Plan limits: Free — 10, Pro — 100, Business — 1,000.
Request body
{
"links": [
{
"originalUrl": "https://example.com/1",
"customSlug": "link-1"
},
{
"originalUrl": "https://example.com/2"
}
]
}Response — 200 OK
{
"success": true,
"data": {
"created": [
{
"shortCode": "link-1",
"shortUrl": "https://go.tooldit.com/link-1",
"originalUrl": "https://example.com/1"
}
],
"failed": [
{
"index": 2,
"error": "Slug already exists"
}
],
"totalCreated": 9,
"totalFailed": 1
}
}Bulk Action
/api/v1/links/bulk-actionPerform an action on multiple links at once.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| shortCodes | string[] | Required | Array of short codes to act on |
| action | string | Required | Action: DELETE, DISABLE, ENABLE |
Response — 200 OK
{
"success": true,
"data": {
"processed": 5,
"failed": 0
}
}Overview Analytics
/api/v1/analytics/overviewReturns aggregated analytics across all your links.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| range | string | Default: 7d | Time range: 7d, 30d, 90d, all |
Response — 200 OK
{
"success": true,
"data": {
"summary": {
"totalClicks": 1240,
"uniqueClicks": 980,
"totalLinks": 45,
"activeLinks": 38
},
"clicksOverTime": [
{ "date": "2026-04-22", "clicks": 180 },
{ "date": "2026-04-23", "clicks": 210 }
],
"topLinks": [
{
"shortCode": "my-video",
"shortUrl": "https://go.tooldit.com/my-video",
"clicks": 320
}
],
"topCountries": [
{ "name": "United States", "clicks": 450 }
],
"topCities": [
{ "name": "New York", "clicks": 120 }
],
"topDevices": [
{ "name": "Mobile", "clicks": 680 }
],
"topBrowsers": [
{ "name": "Chrome", "clicks": 590 }
],
"topReferrers": [
{ "name": "twitter.com", "clicks": 230 }
]
}
}Link Analytics
/api/v1/analytics/links/{shortCode}Returns detailed analytics for a specific link.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| shortCode | string | Required | The link short code |
| range | string | Default: 7d | Time range: 7d, 30d, 90d, all |
Response — 200 OK
{
"success": true,
"data": {
"link": { ...link object },
"summary": { "totalClicks": 320, "uniqueClicks": 280 },
"clicksOverTime": [ ... ],
"topCountries": [ ... ],
"topDevices": [ ... ],
"topReferrers": [ ... ]
}
}Generate Key
/api/v1/keysGenerates a new API key. The raw key value is only returned once.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Required | A human-readable label for the key |
Response — 200 OK
{
"success": true,
"data": {
"id": 1,
"name": "Production key",
"key": "sk_live_abc123...",
"createdAt": "2026-04-29T12:00:00Z"
}
}List Keys
/api/v1/keysReturns all API keys for the authenticated user. Key values are masked.
Response — 200 OK
{
"success": true,
"data": [
{
"id": 1,
"name": "Production key",
"keyPreview": "sk_live_abc1...xyz9",
"lastUsedAt": "2026-04-28T10:00:00Z",
"createdAt": "2026-04-29T12:00:00Z"
}
]
}Delete Key
/api/v1/keys/{id}Permanently revokes and deletes an API key. All requests using it will fail immediately.
Response — 200 OK
{
"success": true,
"data": null
}Regenerate Key
/api/v1/keys/{id}/regenerateIssues a new secret value for an existing key. The old value is immediately invalidated. The new key is returned only once.
Response — 200 OK
{
"success": true,
"data": {
"id": 1,
"name": "Production key",
"key": "sk_live_newvalue...",
"createdAt": "2026-04-29T12:00:00Z"
}
}Add Domain
/api/v1/domainsRegisters a custom domain for use with your short links.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| hostname | string | Required | The domain hostname (e.g. go.yourapp.com) |
Response — 200 OK
{
"success": true,
"data": {
"id": 1,
"hostname": "go.yourapp.com",
"isVerified": false,
"verificationMethod": "DNS_TXT",
"verificationToken": "linkshort-verify=abc123",
"createdAt": "2026-04-29T12:00:00Z"
}
}List Domains
/api/v1/domainsReturns all custom domains for the authenticated user.
Response — 200 OK
{
"success": true,
"data": [
{
"id": 1,
"hostname": "go.yourapp.com",
"isVerified": true,
"sslStatus": "ACTIVE",
"createdAt": "2026-04-29T12:00:00Z"
}
]
}Delete Domain
/api/v1/domains/{id}Removes a custom domain. Links using it will fall back to the default domain.
Response — 200 OK
{
"success": true,
"data": null
}Verify Domain
/api/v1/domains/{id}/verifyTriggers a DNS verification check. Add the TXT record to your DNS before calling this endpoint.
Response — 200 OK
{
"success": true,
"data": {
"id": 1,
"hostname": "go.yourapp.com",
"isVerified": true,
"verifiedAt": "2026-04-29T12:30:00Z"
}
}Register Endpoint
/api/v1/webhooksRegisters a new webhook endpoint to receive event notifications.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | Required | HTTPS URL that will receive events |
| description | string | Optional | Human-readable label |
| events | string[] | Required | Array of event types to subscribe to |
| isActive | boolean | Default: true | Enable or disable the endpoint |
Request example
{
"url": "https://yourapp.com/webhooks/linkshort",
"description": "Production webhook",
"events": ["link.clicked", "link.created"],
"isActive": true
}Response — 200 OK
{
"success": true,
"data": {
"id": 1,
"url": "https://yourapp.com/webhooks/linkshort",
"description": "Production webhook",
"secret": "whsec_abc123...",
"events": ["link.clicked", "link.created"],
"isActive": true,
"failureCount": 0,
"createdAt": "2026-04-29T12:00:00Z"
}
}secret securely — it is only returned at creation time and used to verify webhook signatures.List Endpoints
/api/v1/webhooksReturns all webhook endpoints for the authenticated user.
Response — 200 OK
{
"success": true,
"data": [
{
"id": 1,
"url": "https://yourapp.com/webhooks/linkshort",
"description": "Production webhook",
"events": ["link.clicked", "link.created"],
"isActive": true,
"failureCount": 0,
"createdAt": "2026-04-29T12:00:00Z"
}
]
}Update Endpoint
/api/v1/webhooks/{id}Updates an existing webhook endpoint configuration.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | Optional | New destination URL |
| description | string | Optional | New description |
| events | string[] | Optional | Updated list of subscribed events |
| isActive | boolean | Optional | Enable or disable the endpoint |
Response — 200 OK
{
"success": true,
"data": { ...updated endpoint object }
}Delete Endpoint
/api/v1/webhooks/{id}Permanently removes a webhook endpoint and stops all future deliveries.
Response — 200 OK
{
"success": true,
"data": null
}Rotate Secret
/api/v1/webhooks/{id}/rotate-secretGenerates a new signing secret. Update your verification logic immediately — the old secret is invalidated.
Response — 200 OK
{
"success": true,
"data": {
"id": 1,
"secret": "whsec_newvalue123..."
}
}View Deliveries
/api/v1/webhooks/{id}/deliveriesReturns the delivery history for a webhook endpoint.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| page | number | Default: 0 | Page number |
| size | number | Default: 20 | Items per page |
| status | string | Optional | Filter: SUCCESS, FAILED, PENDING |
Response — 200 OK
{
"success": true,
"data": {
"content": [
{
"id": 1,
"eventType": "link.clicked",
"status": "SUCCESS",
"httpStatus": 200,
"attemptCount": 1,
"createdAt": "2026-04-29T12:00:00Z",
"deliveredAt": "2026-04-29T12:00:01Z"
}
],
"page": 0,
"total": 45
}
}Retry Delivery
/api/v1/webhooks/{id}/deliveries/{deliveryId}/retryManually retries a failed or pending webhook delivery.
Response — 200 OK
{
"success": true,
"data": {
"id": 1,
"status": "SUCCESS",
"httpStatus": 200,
"attemptCount": 2
}
}Test Endpoint
/api/v1/webhooks/{id}/testSends a test event to verify your endpoint is reachable and your signature verification works.
Response — 200 OK
{
"success": true,
"data": {
"httpStatus": 200,
"responseTime": 124,
"message": "Test delivery successful"
}
}link.clicked
Fired whenever someone clicks a short link.
{
"id": "evt_01abc123def456",
"event": "link.clicked",
"timestamp": "2026-04-29T12:00:00Z",
"data": {
"shortCode": "my-video",
"shortUrl": "https://go.tooldit.com/my-video",
"originalUrl": "https://youtube.com/watch?v=abc",
"country": "Pakistan",
"city": "Lahore",
"device": "Mobile",
"browser": "Chrome",
"referrer": "https://twitter.com",
"clickedAt": "2026-04-29T12:00:00Z"
}
}link.expired
Fired when a link passes its expiry time and becomes inactive.
{
"id": "evt_01abc123def457",
"event": "link.expired",
"timestamp": "2026-05-06T00:00:00Z",
"data": {
"shortCode": "my-video",
"shortUrl": "https://go.tooldit.com/my-video",
"originalUrl": "https://youtube.com/watch?v=abc",
"expiredAt": "2026-05-06T00:00:00Z"
}
}link.used
Fired when a single-use link is consumed (first click triggers disable).
{
"id": "evt_01abc123def458",
"event": "link.used",
"timestamp": "2026-04-29T14:00:00Z",
"data": {
"shortCode": "promo-2026",
"shortUrl": "https://go.tooldit.com/promo-2026",
"originalUrl": "https://example.com/promo",
"country": "United States",
"device": "Desktop",
"usedAt": "2026-04-29T14:00:00Z"
}
}link.created
Fired when a new short link is created via API or dashboard.
{
"id": "evt_01abc123def459",
"event": "link.created",
"timestamp": "2026-04-29T10:00:00Z",
"data": {
"shortCode": "my-video",
"shortUrl": "https://go.tooldit.com/my-video",
"originalUrl": "https://youtube.com/watch?v=abc",
"title": "My YouTube Video",
"source": "API",
"createdAt": "2026-04-29T10:00:00Z"
}
}link.deleted
Fired when a link is permanently deleted.
{
"id": "evt_01abc123def460",
"event": "link.deleted",
"timestamp": "2026-04-29T18:00:00Z",
"data": {
"shortCode": "old-link",
"shortUrl": "https://go.tooldit.com/old-link",
"originalUrl": "https://example.com/old",
"deletedAt": "2026-04-29T18:00:00Z"
}
}link.disabled
Fired when a link is manually disabled or auto-disabled after single use.
{
"id": "evt_01abc123def461",
"event": "link.disabled",
"timestamp": "2026-04-29T16:00:00Z",
"data": {
"shortCode": "my-video",
"shortUrl": "https://go.tooldit.com/my-video",
"originalUrl": "https://youtube.com/watch?v=abc",
"reason": "manual",
"disabledAt": "2026-04-29T16:00:00Z"
}
}plan.limit_reached
Fired when a usage limit (links or clicks) is 100% reached for the billing period.
{
"id": "evt_01abc123def462",
"event": "plan.limit_reached",
"timestamp": "2026-04-29T12:00:00Z",
"data": {
"plan": "FREE",
"limitType": "links",
"limit": 100,
"used": 100,
"resetAt": "2026-05-01T00:00:00Z"
}
}plan.limit_warning
Fired when usage reaches 90% of a plan limit, giving time to upgrade or pause campaigns.
{
"id": "evt_01abc123def463",
"event": "plan.limit_warning",
"timestamp": "2026-04-29T11:00:00Z",
"data": {
"plan": "FREE",
"limitType": "clicks",
"limit": 1000,
"used": 900,
"percentUsed": 90,
"resetAt": "2026-05-01T00:00:00Z"
}
}Verifying Webhook Signatures
Every webhook request includes a signature header so you can confirm it originated from LinkShort:
X-LinkShort-Signature: t=1234567890,v1=abc123def456...Verification steps:
- Extract
t(timestamp) andv1(signature) from the header - Build the signed string:
timestamp + "." + raw_request_body - Compute HMAC-SHA256 of the signed string using your webhook secret
- Compare with
v1using a constant-time comparison
const crypto = require('crypto')
function verifyWebhook(rawBody, header, secret) {
const parts = header.split(',')
const timestamp = parts[0].split('=')[1]
const signature = parts[1].split('=')[1]
const signed = timestamp + '.' + rawBody
const expected = crypto
.createHmac('sha256', secret)
.update(signed)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
)
}Postman Collection
Download the official Postman collection to test every API endpoint instantly. All 33 endpoints are pre-configured with variable placeholders.
Download Postman CollectionSetup instructions
- Download the collection and import it into Postman
- Create a Postman environment with these variables:Text
base_url = https://api.tooldit.com api_key = sk_your_key_here - Select your environment and run any request immediately
What's included
- Links — 7 endpoints (create, list, get, update, delete, bulk create, bulk action)
- Analytics — 3 endpoints (overview, link analytics, account stats)
- API Keys — 5 endpoints (generate, list, delete, regenerate, usage)
- Domains — 4 endpoints (add, list, delete, verify)
- Webhooks — 9 endpoints (register, list, get, update, delete, rotate, deliveries, retry, test)
- Auth — 5 endpoints (login, register, verify email, forgot password, reset password)
View the full API reference on this page .