URLs
Create, list, update, and delete short URLs — single and bulk — and pull per-link click analytics.
/api/v1/urls is the workhorse of the Shortnd API. It covers single-link CRUD, bulk operations on up to 100 links per request, and per-link click analytics.
Scopes: urls:read (list / read / clicks), urls:write (create / update / delete / bulk).
Create a single URL
curl -X POST https://shortnd.com/api/v1/urls \
-H "Authorization: Bearer eyJ..." \
-H "Content-Type: application/json" \
-d '{
"targetUrl": "https://acme.com/campaigns/welcome",
"customSlug": "welcome",
"domainId": "domain_uuid"
}'
Request body
Every field except targetUrl is optional.
| Field | Type | Notes |
|---|---|---|
targetUrl | string (URL) | Required. Up to 8 KB. Must be http:// or https://. |
customSlug | string | 3–50 chars, [A-Za-z0-9-], no leading/trailing hyphen. Omit to let Shortnd mint a random short code. |
title | string | Surfaced in dashboard + analytics. |
description | string | Long-form note. |
category | string | Free-form tag for dashboard grouping. |
tags | string[] | Up to 20. |
domainId | UUID | Routes the link through a custom domain. Omit for the platform domain (shortnd.com). The domain must be active in the calling org. |
redirectType | enum | 301 / 302 / 307 / 308. Default 307. |
expiresAt | ISO-8601 | Auto-deactivates the link at this time. |
isPasswordProtected | bool | When true, supply password. |
password | string | Required when isPasswordProtected is true. |
utmSource, utmMedium, utmCampaign, utmTerm, utmContent | string | Server-side UTM injection. |
customUtmParams | record | Extra UTM-style key/value pairs. |
sourceDomain | string | Hint for attribution flows. |
Response
{
"success": true,
"data": {
"id": "12345",
"shortCode": "abc123",
"targetUrl": "https://acme.com/campaigns/welcome",
"customSlug": "welcome",
"organizationId": "org_uuid",
"createdAt": "2026-05-08T10:30:00Z"
}
}
List & paginate
curl https://shortnd.com/api/v1/urls?limit=50&offset=0 \
-H "Authorization: Bearer eyJ..."
| Query | Default | Max |
|---|---|---|
limit | 25 | 100 |
offset | 0 | — |
Results are ordered by createdAt desc and scoped to the calling organization.
Read, update, soft-delete a single URL
| Method | Path | Scope |
|---|---|---|
GET | /api/v1/urls/{id} | urls:read |
PATCH | /api/v1/urls/{id} | urls:write |
DELETE | /api/v1/urls/{id} | urls:write |
PATCH accepts every create field except customSlug (slugs are immutable after creation), plus isActive for pausing without deletion. DELETE is a soft delete — the row is marked inactive and excluded from redirects.
Bulk operations
All three bulk routes share /api/v1/urls/bulk and accept up to 100 items per request. Failures are reported per item; successful items are not rolled back. The api_call meter is charged once per HTTP request, not once per URL — buyers shouldn't be punished for batching.
POST /api/v1/urls/bulk — bulk create
curl -X POST https://shortnd.com/api/v1/urls/bulk \
-H "Authorization: Bearer eyJ..." \
-H "Content-Type: application/json" \
-d '{
"urls": [
{ "targetUrl": "https://example.io/a" },
{ "targetUrl": "https://example.io/b", "customSlug": "promo-2026" },
{ "targetUrl": "https://example.io/c", "title": "Summer launch", "tags": ["launch", "2026"] }
]
}'
Response:
{
"success": true,
"data": {
"requested": 3,
"createdCount": 2,
"failedCount": 1,
"created": [
{ "index": 0, "id": "12345", "shortCode": "abc123", "targetUrl": "...", "customSlug": null }
],
"errors": [
{ "index": 1, "error": "Slug already taken", "code": "SLUG_CONFLICT" }
]
}
}
Per-item error codes:
SLUG_CONFLICT—customSlugis already taken on the destination domain.QUOTA_EXCEEDED— org has hit its plan's link bundle.DOMAIN_INVALID— referenceddomainIdis not active in this org.CREATE_FAILED— generic catch-all (validation, internal error). Inspecterrorfor details.
If every item fails, the response status is 207 Multi-Status to make partial-failure visible without breaking happy-path tooling.
PATCH /api/v1/urls/bulk — bulk update
Each item references the target URL by id and validates against the same schema as PATCH /api/v1/urls/{id}.
curl -X PATCH https://shortnd.com/api/v1/urls/bulk \
-H "Authorization: Bearer eyJ..." \
-H "Content-Type: application/json" \
-d '{
"updates": [
{ "id": 1, "title": "New title", "isActive": true },
{ "id": 2, "tags": ["promo", "summer-2026"] }
]
}'
Per-item error codes: NOT_FOUND (id not in this org) / UPDATE_FAILED.
DELETE /api/v1/urls/bulk — bulk soft-delete
Provide ids via either ?ids=1,2,3 or a JSON body { "ids": [1, 2, 3] }.
curl -X DELETE 'https://shortnd.com/api/v1/urls/bulk?ids=1,2,3' \
-H "Authorization: Bearer eyJ..."
Cross-org rows and missing rows are silently skipped and surface in skippedIds. Soft-delete sets isActive=false.
{
"success": true,
"data": {
"requested": 3,
"deletedCount": 2,
"skippedCount": 1,
"deletedIds": ["1", "2"],
"skippedIds": ["3"]
}
}
Per-URL click analytics
GET /api/v1/urls/{id}/clicks returns geo, device, browser, OS, and referrer breakdowns plus a bucketed time series for a single org-owned URL. Bot traffic is excluded by default (is_bot = false).
curl 'https://shortnd.com/api/v1/urls/12345/clicks?since=2026-04-08T00:00:00Z&groupBy=day&limit=10' \
-H "Authorization: Bearer eyJ..."
| Query | Default | Notes |
|---|---|---|
since | 30 days ago | ISO-8601 |
until | now | ISO-8601 |
groupBy | day | hour or day |
limit | 10 | Top-N for each dimension breakdown (max 50) |
Response shape:
{
"success": true,
"data": {
"urlId": "12345",
"shortCode": "abc123",
"since": "2026-04-08T00:00:00Z",
"until": "2026-05-08T10:30:00Z",
"totals": { "totalClicks": 1840, "uniqueClicks": 1502, "uniqueIps": 1311 },
"breakdowns": {
"country": [{ "country": "US", "clicks": 920 }, { "country": "KE", "clicks": 410 }],
"device": [{ "deviceType": "mobile", "clicks": 1100 }, { "deviceType": "desktop", "clicks": 700 }],
"browser": [{ "browser": "Chrome", "clicks": 1380 }],
"os": [{ "os": "iOS", "clicks": 612 }],
"referrer":[{ "referrerDomain": "twitter.com", "referrerType": "social", "clicks": 220 }]
},
"timeseries": [
{ "bucket": "2026-04-08T00:00:00Z", "clicks": 80, "uniques": 71 }
]
}
}
For org-wide totals across every URL, use GET /api/v1/analytics/overview.
Custom slug rules
- Platform-domain slugs (
shortnd.com/<slug>) are globally unique. - Custom-domain slugs (
<sub>.<your-domain>/<slug>) are unique within that domain only — the samewelcomeslug can exist onacme.comand onexample.iowithout conflict. - Reserved slugs (e.g.,
api,dashboard,admin) are blocked.
URL events on webhooks
Subscribe to url.created, url.updated, url.click_threshold, and url.expired on POST /api/v1/webhooks to react to URL lifecycle events in your own system. See Webhooks for the signature scheme and retry table.