Links

Links are shortened URLs with click tracking. When a link is created, a tracking ID parameter is appended to the original URL, and the result is shortened via a link provider (Bitly or Stacked, depending on org configuration). Click counts are tracked and updated automatically.

All endpoints require authentication via the X-API-Key header. See Authentication.

Each link contains:

  • shortUrl — the shortened URL.
  • longUrl — the original URL with the tracking parameter (stkd_id) appended.
  • clicks — total click count, updated automatically.
  • name — optional display name.
  • dateCreated — creation timestamp.

Internal fields (trackingId, bitlyId, lastLinkClicksUpdate) are excluded from API responses.


POST /links

Request body

FieldTypeRequiredNotes
urlstringYesThe URL to shorten. If missing a protocol, https:// is prepended.
namestringNoOptional display name. Nullable.

Example request

curl -X POST https://<host>/api/links \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/landing-page",
    "name": "Spring Campaign"
  }'

Response

{
  "success": true,
  "data": {
    "_id": "675a...",
    "name": "Spring Campaign",
    "longUrl": "https://example.com/landing-page?stkd_id=675a...",
    "shortUrl": "https://bit.ly/abc123",
    "clicks": 0,
    "dateCreated": 1716000000000,
    "archived": false
  }
}

Errors

StatusMessageCause
400Validation errorMissing or invalid URL.

GET /links

Returns links sorted by dateCreated descending (newest first).

Query parameters

ParameterTypeDefaultNotes
pagenumber00-indexed.
perPagenumber50Items per page.

Example request

curl "https://<host>/api/links?page=0&perPage=25" \
  -H "X-API-Key: your-api-key"

Response

{
  "success": true,
  "data": [
    {
      "_id": "675a...",
      "name": "Spring Campaign",
      "longUrl": "https://example.com/landing-page?stkd_id=675a...",
      "shortUrl": "https://bit.ly/abc123",
      "clicks": 142,
      "dateCreated": 1716000000000,
      "archived": false
    }
  ],
  "pagination": {
    "page": 0,
    "perPage": 25,
    "totalItems": 12,
    "totalPages": 1
  }
}

GET /links/:id

Path parameters

ParameterTypeDescription
idstringLink ID.

Example request

curl https://<host>/api/links/675a... \
  -H "X-API-Key: your-api-key"

Response

{
  "success": true,
  "data": {
    "_id": "675a...",
    "name": "Spring Campaign",
    "longUrl": "https://example.com/landing-page?stkd_id=675a...",
    "shortUrl": "https://bit.ly/abc123",
    "clicks": 142,
    "dateCreated": 1716000000000,
    "archived": false
  }
}

Errors

StatusMessageCause
403ForbiddenLink not found or not in your org.

Response envelope

All endpoints use the standard envelope:

{
  "error": false,
  "success": true,
  "message": "...",
  "code": 200,
  "data": "...",
  "pagination": {}
}

Error responses:

{
  "error": true,
  "success": false,
  "message": "...",
  "code": 400,
  "data": null
}

data is null for a general failure, such as a database error or an upstream timeout. When the request is rejected because of a specific field, data carries the field(s) at fault instead:

{
  "error": true,
  "success": false,
  "message": "phone: Phone parse failed: The string supplied did not seem to be a phone number (input: <masked>)",
  "code": 400,
  "data": {
    "issues": [
      {
        "path": "phone",
        "message": "Phone parse failed: The string supplied did not seem to be a phone number (input: <masked>)"
      }
    ]
  }
}

Each issue carries a path (the dotted path into the request body) and a message. A label is added when the path is not self-describing, which today means custom fields only, for example "path": "fields.68b0a1f39c2e4d0012ab77e5", "label": "Class Year". issues is an array, but treat it as holding at most one entry: validation stops at the first failure.

On this page