> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tracklysms.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Link

> Create a trackable short link for an offer.

Create a trackable short link that routes through your click tracker to an offer destination URL. The short link uses your list's configured link shortener domain, and the click tracker uses your list's click tracking domain (or the platform fallback).

<Note>
  **Requires Offer Management.** This endpoint is only available to accounts with the Offer Management product enabled. Returns `403 product_not_enabled` otherwise.
</Note>

## How It Works

When a recipient clicks the short link, the redirect chain is:

```
Short link (list domain) → Click tracker (list domain) → Offer destination URL
```

1. The short link (`shortUrl`) goes in your SMS message body
2. On click, it redirects through the click tracker (`clickTrackerUrl`) which records the click
3. The click tracker redirects to the offer's destination URL

## Body Parameters

<ParamField body="offerId" type="string">
  Trackly partnership offer ID. **Required** if `externalOfferId` is not provided.
</ParamField>

<ParamField body="externalOfferId" type="string">
  External platform offer ID (e.g. TUNE/HasOffers ID). **Required** if `offerId` is not provided. If both are provided, `offerId` takes priority.
</ParamField>

<ParamField body="listId" type="integer">
  Sending list ID. **Required** if `phoneNumber` is not provided. The list determines which link shortener and click tracking domains are used.
</ParamField>

<ParamField body="phoneNumber" type="string">
  Sending list phone number in E.164 format (e.g. `+18005551234`). **Required** if `listId` is not provided. Alternative way to identify the sending list.
</ParamField>

## Response Fields

<ResponseField name="shortUrl" type="string">
  Full short URL to include in your SMS message body. Uses the list's configured link shortener domain.
</ResponseField>

<ResponseField name="linkId" type="string">
  Unique short link ID. Appended to the domain to form the `shortUrl`.
</ResponseField>

<ResponseField name="clickTrackerUrl" type="string">
  The click tracker URL that the short link redirects through. Records the click before forwarding to the offer destination.
</ResponseField>

<ResponseField name="destinationUrl" type="string">
  The offer's final destination URL that the recipient lands on after click tracking.
</ResponseField>

<ResponseField name="domain" type="string">
  The link shortener domain used for this short link.
</ResponseField>

<ResponseField name="offerId" type="string">
  The Trackly partnership offer ID the link is associated with.
</ResponseField>

## Examples

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://api.tracklysms.com/api/v2/links \
    -H "X-Api-Key: trk_your_api_key_here" \
    -H "Content-Type: application/json" \
    -d '{
      "offerId": "6789abcd1234ef5678901234",
      "listId": 42
    }'
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.tracklysms.com/api/v2/links",
      headers={
          "X-Api-Key": "trk_your_api_key_here",
          "Content-Type": "application/json",
      },
      json={
          "offerId": "6789abcd1234ef5678901234",
          "listId": 42,
      },
  )

  data = response.json()
  print(data["shortUrl"])  # e.g. https://yourdomain.com/Ab3kX9
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.tracklysms.com/api/v2/links", {
    method: "POST",
    headers: {
      "X-Api-Key": "trk_your_api_key_here",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      offerId: "6789abcd1234ef5678901234",
      listId: 42,
    }),
  });

  const data = await response.json();
  console.log(data.shortUrl);
  ```
</RequestExample>

### Using External Offer ID and Phone Number

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.tracklysms.com/api/v2/links \
    -H "X-Api-Key: trk_your_api_key_here" \
    -H "Content-Type: application/json" \
    -d '{
      "externalOfferId": "12345",
      "phoneNumber": "+18005551234"
    }'
  ```

  ```python Python theme={null}
  response = requests.post(
      "https://api.tracklysms.com/api/v2/links",
      headers={
          "X-Api-Key": "trk_your_api_key_here",
          "Content-Type": "application/json",
      },
      json={
          "externalOfferId": "12345",
          "phoneNumber": "+18005551234",
      },
  )
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.tracklysms.com/api/v2/links", {
    method: "POST",
    headers: {
      "X-Api-Key": "trk_your_api_key_here",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      externalOfferId: "12345",
      phoneNumber: "+18005551234",
    }),
  });

  const data = await response.json();
  console.log(data.shortUrl);
  ```
</CodeGroup>

<ResponseExample>
  ```json Success (201) theme={null}
  {
    "shortUrl": "https://yourdomain.com/Ab3kX9",
    "linkId": "Ab3kX9",
    "clickTrackerUrl": "https://ct.yourdomain.click/c/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "destinationUrl": "https://example.com/offer?aff_sub={{sendId}}",
    "domain": "https://yourdomain.com",
    "offerId": "6789abcd1234ef5678901234"
  }
  ```

  ```json Error - Product Not Enabled (403) theme={null}
  {
    "error": "This API requires the Offer Management product. Please upgrade your account to access link creation.",
    "code": "product_not_enabled"
  }
  ```

  ```json Error - No Domain Configured (409) theme={null}
  {
    "error": "No link domain configured for this list",
    "code": "no_domain_configured"
  }
  ```
</ResponseExample>

## Error Codes

| HTTP Status | Error Code                | Description                                                                                                     |
| ----------- | ------------------------- | --------------------------------------------------------------------------------------------------------------- |
| 400         | `missing_offer_id`        | Neither `offerId` nor `externalOfferId` was provided.                                                           |
| 400         | `missing_list_identifier` | Neither `listId` nor `phoneNumber` was provided.                                                                |
| 400         | `invalid_phone`           | The `phoneNumber` field is not a valid E.164 phone number.                                                      |
| 403         | `product_not_enabled`     | Your account does not have the Offer Management product enabled.                                                |
| 404         | `offer_not_found`         | No active partnership offer found matching the provided ID.                                                     |
| 404         | `list_not_found`          | The sending list was not found or does not belong to your account.                                              |
| 409         | `no_domain_configured`    | The sending list does not have a link shortener domain configured. Assign a domain in your list settings first. |
| 500         | `id_collision`            | Failed to generate a unique link ID after multiple attempts. Retry the request.                                 |

## Macros & URL Parameters

The click tracker supports **macros** — placeholder tokens in your offer's Destination URL Template that get replaced with real values at click time. You can configure the Destination URL Template on your offer in the UI (under Offer Management > Offers).

### Supported Macros

Use these in your Destination URL Template with curly brace syntax (e.g. `{click_id}`):

| Macro               | Description                                          |
| ------------------- | ---------------------------------------------------- |
| `{click_id}`        | Unique click identifier                              |
| `{transaction_id}`  | Alias for `{click_id}` (HasOffers terminology)       |
| `{affiliate_id}`    | Affiliate identifier                                 |
| `{offer_id}`        | Offer identifier                                     |
| `{account_id}`      | Account identifier                                   |
| `{sub1}` – `{sub5}` | Sub-tracking parameters passed on the short link URL |
| `{source}`          | Traffic source identifier                            |
| `{creative}`        | Creative identifier                                  |
| `{cost}`            | Cost value                                           |
| `{ip}`              | Visitor's IP address                                 |
| `{user_agent}`      | Visitor's user agent string                          |
| `{datetime}`        | Click timestamp (ISO 8601)                           |
| `{country_code}`    | Visitor's country code (from GeoIP)                  |

#### Example Destination URL Template

```
https://example.com/lp?clickid={click_id}&aff={affiliate_id}&s1={sub1}&s2={sub2}
```

If no macros are present in the template, `click_id` and any sub-tracking parameters are automatically appended as query parameters.

### Passing Macros via the Short Link

Any query parameters appended to the short link URL at click time are forwarded through the click tracker and used to populate macros. This lets you pass dynamic per-recipient values without creating separate links.

For example, if your short link is `https://yourdomain.com/Ab3kX9`, you can append parameters in the SMS body:

```
https://yourdomain.com/Ab3kX9?sub1=campaign_a&sub2=user_123&source=sms
```

The click tracker picks up `sub1`, `sub2`, and `source` from the query string, substitutes them into the Destination URL Template macros, and redirects the visitor to the final URL with all values filled in.

This means you can create one link per offer/list combination and personalize it per message by appending query parameters in your message body.

## Notes

* The offer must be **active** (`status: 'active'`) to create links.
* The sending list must be **active** and have a link shortener domain configured in its settings.
* If the list has a custom click tracking domain configured, it will be used. Otherwise, the platform's default click tracker is used.
* Each call creates a new unique short link. There is no deduplication — calling this endpoint twice with the same parameters produces two different links.

## Next Steps

<CardGroup cols={2}>
  <Card title="Link Tracking" icon="link" href="/guides/link-tracking/overview">
    Track clicks and attribute conversions
  </Card>

  <Card title="Create Offer" icon="tag" href="/api-reference/v2/offers/create-offer">
    Set up an offer before creating links
  </Card>
</CardGroup>
