Shortnd Docs

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.

FieldTypeNotes
targetUrlstring (URL)Required. Up to 8 KB. Must be http:// or https://.
customSlugstring3–50 chars, [A-Za-z0-9-], no leading/trailing hyphen. Omit to let Shortnd mint a random short code.
titlestringSurfaced in dashboard + analytics.
descriptionstringLong-form note.
categorystringFree-form tag for dashboard grouping.
tagsstring[]Up to 20.
domainIdUUIDRoutes the link through a custom domain. Omit for the platform domain (shortnd.com). The domain must be active in the calling org.
redirectTypeenum301 / 302 / 307 / 308. Default 307.
expiresAtISO-8601Auto-deactivates the link at this time.
isPasswordProtectedboolWhen true, supply password.
passwordstringRequired when isPasswordProtected is true.
utmSource, utmMedium, utmCampaign, utmTerm, utmContentstringServer-side UTM injection.
customUtmParamsrecordExtra UTM-style key/value pairs.
sourceDomainstringHint 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..."
QueryDefaultMax
limit25100
offset0

Results are ordered by createdAt desc and scoped to the calling organization.

Read, update, soft-delete a single URL

MethodPathScope
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_CONFLICTcustomSlug is already taken on the destination domain.
  • QUOTA_EXCEEDED — org has hit its plan's link bundle.
  • DOMAIN_INVALID — referenced domainId is not active in this org.
  • CREATE_FAILED — generic catch-all (validation, internal error). Inspect error for 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..."
QueryDefaultNotes
since30 days agoISO-8601
untilnowISO-8601
groupBydayhour or day
limit10Top-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 same welcome slug can exist on acme.com and on example.io without 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.