# URL Shortener API V3

Create shortened links and vanity links through a URL shortener configured for your Sprinklr partner. Sprinklr provides its own shortening utility, `spr.ly`, and can also front third-party providers.

This guide covers both V3 Links operations and includes migration guidance for developers moving from the V2 URL Shortener APIs.

## On this page

1. [Overview](#overview)
2. [Base URL and environments](#base-url-and-environments)
3. [Authentication](#authentication)
4. [Operations](#operations)
5. [Field reference](#field-reference)
6. [Worked examples](#worked-examples)
7. [Response format and status codes](#response-format-and-status-codes)
8. [Migrating from V2](#migrating-from-v2)
9. [Common tasks](#common-tasks)
10. [Best practices](#best-practices)
11. [Troubleshooting](#troubleshooting)
12. [Related articles](#related-articles)


## Overview

The URL Shortener API exposes two operations, and in practice you use them together.

| Operation | Method and path | Purpose |
|  --- | --- | --- |
| Fetch shorteners | `GET /link/shorteners` | List the URL shorteners available to your partner |
| Shorten a URL | `POST /link/shorten` | Create a short link or a vanity link |


Call `GET /link/shorteners` first. `POST /link/shorten` requires a `urlShortenerId`, and the shorteners list is where you obtain it. Cache the result, because the list changes only when an administrator adds or removes a shortener in Sprinklr.

### Standard links and vanity links

The API produces two kinds of short link.

**Standard short link.** Send `link` and `urlShortenerId`. Sprinklr generates the link hash for you and the response carries `campaignId: -11`.

**Vanity link.** Additionally send `name`. That value becomes the link hash verbatim, so `name: "SpringSale"` produces a short link ending in `/SpringSale`. Vanity links carry `campaignId: -10` and populate the `domain` field.

Vanity links require a shortener whose `canUseForVanityLink` field is `true`. Read that flag from `GET /link/shorteners` before you attempt a vanity link.

> **The `name` field is the vanity hash, not the name of the shortener.** The shortener's own name is `UrlShortener.name`, returned by `GET /link/shorteners`, and it is never sent to `POST /link/shorten`. Send `name` only when you want a vanity link with that exact hash.


## Base URL and environments

```
https://api3.sprinklr.com/{env}/api/v3/link/shorten
https://api3.sprinklr.com/{env}/api/v3/link/shorteners
```

Replace `{env}` with the environment your partner is provisioned on. The supported values are:

`prod`, `prod0`, `prod2`, `prod3`, `prod4`, `prod5`, `prod6`, `prod8`, `prod11`, `prod12`, `prod15`, `prod16`, `prod17`, `prod18`, `prod19`, `prod21`, `prod25`

Use the environment your API key was issued against. Calling a different environment returns `401` rather than redirecting. If you are unsure which environment applies, check the host you use to sign in to Sprinklr, or contact your Sprinklr account team.

## Authentication

Both operations require two authentication headers. **Send both on every request.**

| Header | Value | Description |
|  --- | --- | --- |
| `Authorization` | `Bearer {{accessToken}}` | Bearer token used to authenticate the user with the server |
| `Key` | `{{apiKey}}` | API key that authenticates your application with the server |
| `Content-Type` | `application/json` | Required on `POST /link/shorten` |
| `Accept` | `application/json` | Acceptable response type |


Only **one access token and refresh token pair exists per API key** at a time. Requesting a new pair invalidates the previous one. If two of your services shorten links, either issue them separate API keys or centralise token refresh behind a single component.

## Operations

### Fetch all available URL shorteners

```
GET /link/shorteners
```

Returns every URL shortener visible to the partner behind your credentials, as an array.

- **Parameters:** none
- **Request body:** none
- **Success:** `200`


Use this operation to obtain two things: the `id` you pass as `urlShortenerId` when shortening, and the `canUseForVanityLink` flag that tells you whether the shortener supports vanity links.

The response is returned in a single call and is scoped to your partner. Cache it at start-up rather than fetching it before every shortening request.

### Shorten a URL or create a vanity link

```
POST /link/shorten
```

Creates a short link using the specified shortener.

- **Request body:** required
- **Required fields:** `link`, `urlShortenerId`
- **Success:** `201 Created`


Each call creates a new short link. Posting the same `link` and `urlShortenerId` twice produces two short links with two distinct `id` values, so maintain your own mapping of original URL to short link if you need to avoid duplicates.

Store `id`, `linkHash`, and `shortLink` from the response at creation time. These values are returned once, when the link is created.

## Field reference

### Request body for POST /link/shorten

| Field | Type | Required | Description |
|  --- | --- | --- | --- |
| `link` | string | **Yes** | The URL to be shortened. Provide a fully qualified URL including the scheme |
| `urlShortenerId` | string | **Yes** | The URL shortener to use. Obtain this from `GET /link/shorteners` |
| `name` | string | No | Unique name or URL hash. When supplied, it becomes the link hash, producing a vanity link |
| `description` | string | No | Description of the URL being shortened |


### URL shortener object

Returned as an array by `GET /link/shorteners`.

| Field | Type | Description |
|  --- | --- | --- |
| `id` | string | ID of the shortener. Pass this as `urlShortenerId` when shortening |
| `name` | string | Name of the shortener |
| `provider` | string | Shortener service provider |
| `canUseForVanityLink` | boolean | Whether the shortener can create vanity links |


Treat a missing `canUseForVanityLink` as `false` rather than assuming vanity support. Select shorteners by `name` for human-facing choices and by `provider` when behaviour depends on the underlying service. Keep the selection configurable rather than hard-coding an `id`, because shortener IDs differ across environments and partners.

### Short link object

Returned as `data` by `POST /link/shorten`.

| Field | Type | Description |
|  --- | --- | --- |
| `id` | integer (int64) | Short link ID |
| `urlShortenerId` | string | The URL shortener that was used |
| `linkHash` | string | Unique hash for the shortened link |
| `originalLink` | string | The original URL that was shortened |
| `shortLink` | string | The short URL. This is the value you publish |
| `campaignId` | integer (int64) | `-10` for vanity links, `-11` for links shortened directly |
| `description` | string | Short URL description |
| `domain` | string | Domain used to shorten the link |
| `provider` | string | URL provider used for shortening |
| `createdTime` | integer (int64) | Creation time of the short link |
| `modifiedTime` | integer (int64) | Last modified time of the short link |
| `archived` | boolean | Whether the short link is archived |
| `postId` | integer (int64) | Post ID the link is associated with |


**Six fields are consistently present on a creation response:** `id`, `urlShortenerId`, `linkHash`, `originalLink`, `shortLink`, and `campaignId`. `domain` is additionally returned for vanity links. The remaining fields become populated later in a link's lifecycle, for example `postId` once the link is attached to a published post. **Null-check any field beyond the six above.**

Note the mixed identifier types: `id` is a 64-bit number, while `urlShortenerId` is a string ID. Do not store `id` in a field sized for a 24-character hexadecimal identifier.

### How linkHash relates to your request

| Case | What you send | Resulting `linkHash` | `campaignId` |
|  --- | --- | --- | --- |
| Standard link | `link` + `urlShortenerId` | System-generated alphanumeric token | `-11` |
| Vanity link | `link` + `urlShortenerId` + `name` | Exactly the `name` you sent | `-10` |


Read `linkHash` and `shortLink` directly from the response. Do not derive either value from `id`.

## Worked examples

Replace `{{accessToken}}`, `{{apiKey}}`, and `{env}` with your own values.

### List available shorteners

```bash
curl -X GET \
  'https://api3.sprinklr.com/{env}/api/v3/link/shorteners' \
  -H 'Authorization: Bearer {{accessToken}}' \
  -H 'Key: {{apiKey}}' \
  -H 'Accept: application/json'
```

Example response:

```json
{
  "data": [
    {
      "id": "0000000000000000000000a1",
      "name": "Corporate spr.ly",
      "provider": "SPRINKLR",
      "canUseForVanityLink": true
    },
    {
      "id": "0000000000000000000000b2",
      "name": "Marketing Bitly",
      "provider": "BITLY",
      "canUseForVanityLink": false
    }
  ],
  "errors": []
}
```

### Create a standard short link

```bash
curl -X POST \
  'https://api3.sprinklr.com/{env}/api/v3/link/shorten' \
  -H 'Authorization: Bearer {{accessToken}}' \
  -H 'Key: {{apiKey}}' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{
    "link": "https://www.example.com/spring-campaign-landing-page",
    "urlShortenerId": "0000000000000000000000a1"
  }'
```

Example response, returned with status `201 Created`:

```json
{
  "data": {
    "id": 600000000,
    "urlShortenerId": "0000000000000000000000a1",
    "linkHash": "6003ZAI5v",
    "originalLink": "https://www.example.com/spring-campaign-landing-page",
    "shortLink": "https://spr.ly/6003ZAI5v",
    "campaignId": -11
  },
  "errors": []
}
```

Publish `data.shortLink`. Store `data.id` if you need to correlate the link later.

### Create a vanity short link

Send `name` as the hash you want, using a shortener where `canUseForVanityLink` is `true`.

```bash
curl -X POST \
  'https://api3.sprinklr.com/{env}/api/v3/link/shorten' \
  -H 'Authorization: Bearer {{accessToken}}' \
  -H 'Key: {{apiKey}}' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{
    "link": "https://www.example.com/spring-campaign-landing-page",
    "urlShortenerId": "0000000000000000000000a1",
    "name": "SpringSale"
  }'
```

Example response:

```json
{
  "data": {
    "id": 600000001,
    "urlShortenerId": "0000000000000000000000a1",
    "linkHash": "SpringSale",
    "originalLink": "https://www.example.com/spring-campaign-landing-page",
    "shortLink": "https://spr.ly/SpringSale",
    "campaignId": -10,
    "domain": "spr.ly"
  },
  "errors": []
}
```

`linkHash` echoes `name`, `campaignId` is `-10`, and `domain` is populated. All three confirm you received a vanity link rather than a standard one.

Vanity hashes occupy a shared namespace per shortener domain, so a given hash can be claimed only once. Choose names unlikely to collide, and handle a rejection by retrying with a different `name`.

### End-to-end in Python

```python
import requests

BASE = "https://api3.sprinklr.com/{env}/api/v3".format(env="prod0")
HEADERS = {
    "Authorization": "Bearer {{accessToken}}",
    "Key": "{{apiKey}}",
    "Content-Type": "application/json",
    "Accept": "application/json",
}


def get_shorteners():
    r = requests.get(BASE + "/link/shorteners", headers=HEADERS, timeout=30)
    r.raise_for_status()
    return r.json().get("data") or []


def pick_shortener(shorteners, need_vanity=False):
    for s in shorteners:
        if not need_vanity:
            return s
        if s.get("canUseForVanityLink") is True:
            return s
    raise RuntimeError("No suitable shortener is configured for this partner")


def shorten(link, shortener_id, name=None):
    body = {"link": link, "urlShortenerId": shortener_id}
    if name:
        body["name"] = name

    r = requests.post(BASE + "/link/shorten", headers=HEADERS,
                      json=body, timeout=30)

    if r.status_code not in (200, 201):
        r.raise_for_status()

    payload = r.json()
    errors = payload.get("errors") or []
    if errors:
        raise RuntimeError("Shortening failed: {}".format(errors))

    return payload["data"]


shorteners = get_shorteners()
chosen = pick_shortener(shorteners, need_vanity=True)
result = shorten(
    "https://www.example.com/spring-campaign-landing-page",
    chosen["id"],
    name="SpringSale",
)
print(result["shortLink"])
```

Two details are deliberate. The status check accepts both `200` and `201`, and the `errors` array is inspected even on a success status.

### End-to-end in JavaScript

```javascript
const BASE = "https://api3.sprinklr.com/prod0/api/v3";
const HEADERS = {
  "Authorization": "Bearer {{accessToken}}",
  "Key": "{{apiKey}}",
  "Content-Type": "application/json",
  "Accept": "application/json",
};

async function getShorteners() {
  const res = await fetch(`${BASE}/link/shorteners`, { headers: HEADERS });
  if (!res.ok) throw new Error(`Fetch shorteners failed: ${res.status}`);
  const body = await res.json();
  return body.data ?? [];
}

async function shorten(link, urlShortenerId, name) {
  const payload = { link, urlShortenerId };
  if (name) payload.name = name;

  const res = await fetch(`${BASE}/link/shorten`, {
    method: "POST",
    headers: HEADERS,
    body: JSON.stringify(payload),
  });

  if (res.status !== 200 && res.status !== 201) {
    throw new Error(`Shorten failed: ${res.status}`);
  }

  const body = await res.json();
  if (body.errors?.length) {
    throw new Error(`Shorten failed: ${JSON.stringify(body.errors)}`);
  }
  return body.data;
}

const shorteners = await getShorteners();
const vanityCapable = shorteners.find((s) => s.canUseForVanityLink === true);
const link = await shorten(
  "https://www.example.com/spring-campaign-landing-page",
  vanityCapable.id,
  "SpringSale",
);
console.log(link.shortLink);
```

## Response format and status codes

### Response envelope

Every Sprinklr V3 response is wrapped in a standard envelope:

```json
{
  "data": { },
  "errors": [ ]
}
```

`GET /link/shorteners` returns an array in `data`. `POST /link/shorten` returns a single object in `data`.

Each entry in `errors` contains:

| Field | Type | Description |
|  --- | --- | --- |
| `id` | string | Error identifier |
| `code` | string | Error code |
| `message` | string | Human-readable message |


**Bind your client to this envelope**, and inspect the `errors` array even when the HTTP status indicates success.

### Status codes

| Code | Meaning | Handling |
|  --- | --- | --- |
| `200` | Success | Returned by `GET /link/shorteners` |
| `201` | Created | Returned by `POST /link/shorten` on success |
| `400` | Bad request | A required field is missing, the URL is malformed, or the vanity name was rejected |
| `401` | Unauthorized | Expired token, wrong environment, or a missing authentication header |
| `403` | Forbidden | Your user lacks permission for the shortener, or the partner is not entitled |
| `404` | Not found | Typically an unrecognised `urlShortenerId` |


Accept both `200` and `201` as success on `POST /link/shorten`, or check for any `2xx`.

Rate limiting is applied at the Sprinklr API gateway across the platform. Implement retry with exponential backoff for `429` and `5xx` responses, and honour the `Retry-After` header when it is present. Because shortening is not idempotent, retry only on network-level failures and on `429`, and be aware that retrying after a timeout may create a second link.

### About error messages

Validation errors for shortening are surfaced from the external shortening provider rather than generated by Sprinklr. This has three practical consequences:

- Error message text and codes are not stable across providers. A partner using Bitly and a partner using `spr.ly` can receive different error bodies for the same mistake.
- Branch your error handling on the HTTP status code rather than on the message text.
- Log the full `errors` array and surface the message to an operator rather than attempting to parse it.


## Migrating from V2

### The short version

Change `v2` to `v3` in the path. For most integrations, that is the entire migration.

|  | V2 | V3 |
|  --- | --- | --- |
| Shorten | `POST /{env}/api/v2/link/shorten` | `POST /{env}/api/v3/link/shorten` |
| List shorteners | `GET /{env}/api/v2/link/shorteners` | `GET /{env}/api/v3/link/shorteners` |
| Request fields | `link`, `urlShortenerId`, `name` | Unchanged |
| Required fields | `link`, `urlShortenerId` | Unchanged |
| Response envelope | `{data, errors}` | Unchanged |
| Authentication headers | `Authorization`, `Key` | Unchanged |
| Success status on shorten | `200` | `201` |


### The two things that genuinely differ

**Success status code.** V2 returned `200` on shortening. V3 returns `201 Created`. A client written as `if (status === 200)` will treat a successful V3 creation as a failure. **This is the most likely cause of a broken migration.** Widen the check to accept `200` and `201`, or accept any `2xx`.

**Link hash format for standard links.** In V2, the standard-link `linkHash` was the decimal string form of the numeric `id`, for example `id: 101612830` with `linkHash: "101612830"`. In V3, standard links return an alphanumeric token unrelated to the `id`. If any code derives one value from the other, or validates that `linkHash` is numeric, update it to read `linkHash` and `shortLink` directly from the response.

Vanity links are unaffected, because their hash has always been the `name` you supply.

### What did not change

Request field names and semantics, the required field set, the `{data, errors}` envelope, the `campaignId` sentinels (`-10` for vanity, `-11` for standard), authentication, and the vanity-capability rule are all identical. Error responses are also unchanged, because validation errors originate from the external shortening provider in both versions.

### Migration checklist

1. Change `api/v2` to `api/v3` on both endpoints.
2. Widen the success check on `POST /link/shorten` to accept `201`.
3. Remove any code that derives `linkHash` from `id` or assumes `linkHash` is numeric.
4. Add null checks for `domain`, `createdTime`, `modifiedTime`, `provider`, `archived`, and `postId`.
5. Continue inspecting the `errors` array on success statuses.
6. Re-verify vanity flows against a shortener where `canUseForVanityLink` is `true`.


## Common tasks

### Shorten a link for a scheduled campaign post

Fetch the shortener list once at start-up and cache it. For each URL, call `POST /link/shorten` with `link` and the cached `urlShortenerId`, then publish `data.shortLink`. Store `data.id` and `data.linkHash` alongside your campaign record so reporting can be correlated later.

### Reserve a memorable vanity link

Select a shortener where `canUseForVanityLink` is `true`, then post with `name` set to the hash you want. Confirm success by checking that `data.linkHash` equals the `name` you sent and that `campaignId` is `-10`. If the hash comes back different, you did not receive the vanity link you requested, so do not publish it as one.

### Build a link-shortening service for internal teams

Expose a thin wrapper over `POST /link/shorten` and maintain your own mapping from original URL to short link. Check your mapping first and call Sprinklr only on a miss. This keeps your short-link inventory stable and avoids creating a new link each time the same URL is submitted.

### Choose between multiple configured shorteners

`GET /link/shorteners` returns `name` and `provider` for each entry. Use `name` for human-facing selection and `provider` when behaviour depends on the underlying service. Keep the choice configurable so the same code works across environments and partners.

## Best practices

- **Cache the shorteners list.** It changes only when an administrator adds or removes a shortener. Fetch it at start-up rather than before every shortening call.
- **Persist the creation response.** Store `id`, `linkHash`, and `shortLink` when the link is created. These values are returned at creation time.
- **Accept `200` and `201`** as success on shortening.
- **Null-check optional fields.** Depend only on `id`, `urlShortenerId`, `linkHash`, `originalLink`, `shortLink`, and `campaignId`.
- **Read the scheme from `shortLink`.** Embed the returned string as-is rather than reconstructing the URL from `domain` and `linkHash`.
- **Deduplicate before retrying.** Shortening is not idempotent, so a retry after a timeout can create a duplicate link.
- **Check `canUseForVanityLink` before requesting a vanity link**, and treat a missing value as `false`.
- **Do not look up `campaignId` values of `-10` or `-11`.** These are sentinel values indicating a vanity or standard link, not references to a real campaign.
- **Keep shortener IDs in configuration**, not in code, because they differ across environments and partners.


> **Firebase shorteners.** Google deprecated its Firebase URL shortener, and Firebase shorteners are no longer supported in Sprinklr effective **25 August 2025**. If your integration selects a shortener by `provider`, ensure it does not target Firebase.


## Troubleshooting

| Symptom | Likely cause | Resolution |
|  --- | --- | --- |
| `401` on every call | Wrong `{env}`, expired access token, or a missing `Key` header | Confirm the environment, refresh the token, and send both authentication headers |
| `400` with an unclear message | A required field is missing, or the provider rejected the request | Confirm `link` and `urlShortenerId` are both present and that `link` includes a scheme |
| `404` on shortening | `urlShortenerId` does not exist or is not visible to your user | Re-fetch `GET /link/shorteners` and use an `id` from that response |
| Vanity name ignored, standard hash returned | The shortener does not support vanity links | Check `canUseForVanityLink` and select a different shortener |
| Vanity request rejected | The hash is already claimed on that domain | Retry with a different `name` |
| Client fails to deserialize the response | Client bound to the inner object rather than the envelope | Bind to `{data, errors}` and read the payload from `data` |
| Duplicate short links after a retry | Shortening is not idempotent | Deduplicate against your own mapping before retrying |
| Success status but nothing usable in `data` | An error was returned alongside a success status | Inspect the `errors` array on every response |


## Related articles

**Getting started**

- [API Overview](https://dev.sprinklr.com/api-overview)
- [API Key and Secret Generation](https://dev.sprinklr.com/api-key-and-secret-generation)
- [Authorize](https://dev.sprinklr.com/authorize)
- [APIs by environment](https://dev.sprinklr.com/apis)


**Reference**

- [REST API Errors and Status Codes](https://dev.sprinklr.com/rest-api-error-and-status-codes)
- [FAQs](https://dev.sprinklr.com/faqs)
- [Help Center](https://dev.sprinklr.com/help-center)


**V2 documentation**

- [URL Shortener overview](https://dev.sprinklr.com/url-shortener)
- [Create URL Shortener](https://dev.sprinklr.com/create-url-shortener)


**Product documentation**

- [Configure a URL shortener](https://www.sprinklr.com/help/articles/url-shorteners/configure-a-url-shortener/6467ab928ea3c9635cf374a9)
- [The Sprinklr APIs](https://www.sprinklr.com/help/articles/platform-modules/the-sprinklr-apis/633c5c2359534970b26f96ae)