API · v1

Change a campaign's link through the API

List the Instagram accounts and campaigns of your DM Link account and change where a campaign's link points, from any other system.

The API is available on the Infinity plan. Keys are created and only work while the account is on Infinity: if the account leaves Infinity its keys stop answering right away, and they work again once it is back. See plans.

How it works

When a DM Link campaign sends its link in a DM, it does not send the final address directly. It sends a DM Link tracked link, like https://dmlink.app/r/abc123, that redirects to the configured destination.

The API changes that destination. So:

The code at the end of the tracked link (abc123) is the slug. It identifies which link to change. A typical use: a bot that manages WhatsApp groups swaps the link to the next group when the current one fills up.

1. Get an API key

  1. Sign in to DM Link as the owner or an admin of the account.
  2. Go to Settings → API keys.
  3. In “What will you use this key for?”, write a name just so you recognize it later (e.g. my WhatsApp groups system) and click Create key.
  4. Copy the key right away. It starts with dml_ and is shown only once. After that not even DM Link can show it again.

On the same screen you see the active keys, deactivate any of them (it stops working immediately) and follow the history of the latest link changes made through the API.

One account, one key, every Instagram connected to it

To check that a key belongs to the right account, call GET /api/v1/instagram-accounts: if the Instagram you expect is not in the list, the key belongs to another account.

2. Base URL and authentication

Base URL

https://dmlink.app/api/v1

Send the key on every call

Authorization: Bearer dml_YOUR_KEY

X-API-Key: dml_YOUR_KEY is accepted too. Responses are always JSON: {"success": true, "data": ...} or {"success": false, "error": {"code": "...", "message": "..."}}.

MethodPathWhat it does
GET/api/v1/instagram-accountsList the Instagram accounts connected to the account
GET/api/v1/campaignsList the campaigns and their links, for every Instagram
GET/api/v1/campaigns?instagram=@usernameList only one Instagram's campaigns
GET/api/v1/links/{slug}See where a link points now
PATCH (or PUT)/api/v1/links/{slug}Change where a link points

3. List the account's Instagram accounts

GEThttps://dmlink.app/api/v1/instagram-accounts

Returns the Instagram accounts connected to the key's account, in the order they were connected. Use it to let someone pick which Instagram to work with.

curl https://dmlink.app/api/v1/instagram-accounts \
  -H "Authorization: Bearer $DMLINK_API_KEY"

Response

{
  "success": true,
  "data": [
    {
      "id": "cm9z8y7x6w5v4u",
      "username": "mybrand",
      "name": "My Brand",
      "needsReconnect": false,
      "campaignCount": 1
    },
    {
      "id": "cm1q2w3e4r5t6y",
      "username": "another.profile",
      "name": null,
      "needsReconnect": false,
      "campaignCount": 2
    }
  ]
}
FieldMeaning
idThe Instagram account's id in DM Link (never changes).
usernameThe Instagram handle, without the @.
nameProfile name (may be null).
needsReconnecttrue when Meta dropped the connection: this Instagram's campaigns stop answering until the owner reconnects it in DM Link. Changing links keeps working.
campaignCountHow many campaigns this Instagram has (active and paused).

4. List the campaigns of an Instagram account

GEThttps://dmlink.app/api/v1/campaigns?instagram=@username

Returns the campaigns, newest first, with their tracked links and which Instagram each one belongs to. Use it to find the slug of the link you want to change.

About each campaign's links:

curl "https://dmlink.app/api/v1/campaigns?instagram=mybrand" \
  -H "Authorization: Bearer $DMLINK_API_KEY"

Response

{
  "success": true,
  "data": [
    {
      "id": "cm1a2b3c4d5e6f",
      "name": "VIP group",
      "active": true,
      "instagram": { "id": "cm9z8y7x6w5v4u", "username": "mybrand" },
      "links": [
        {
          "slug": "abc123",
          "position": 1,
          "label": "Primary campaign link",
          "destinationUrl": "https://chat.whatsapp.com/CURRENT_GROUP",
          "trackedUrl": "https://dmlink.app/r/abc123",
          "updatedAt": "2026-10-02T12:00:00.000Z"
        }
      ]
    }
  ]
}
Tip: a slug never changes, even when the destination does. Look it up once, store it in your integration's settings and reuse it.
GEThttps://dmlink.app/api/v1/links/{slug}
curl https://dmlink.app/api/v1/links/abc123 \
  -H "Authorization: Bearer $DMLINK_API_KEY"

Response

{
  "success": true,
  "data": {
    "campaignId": "cm1a2b3c4d5e6f",
    "slug": "abc123",
    "position": 1,
    "label": "Primary campaign link",
    "destinationUrl": "https://chat.whatsapp.com/CURRENT_GROUP",
    "trackedUrl": "https://dmlink.app/r/abc123",
    "updatedAt": "2026-10-02T12:00:00.000Z"
  }
}

6. Change where a link points

PATCHhttps://dmlink.app/api/v1/links/{slug}

Body (JSON)

{ "destinationUrl": "https://chat.whatsapp.com/NEW_GROUP" }

curl

curl -X PATCH https://dmlink.app/api/v1/links/abc123 \
  -H "Authorization: Bearer $DMLINK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"destinationUrl": "https://chat.whatsapp.com/NEW_GROUP"}'

JavaScript (Node)

// Node 18+ (built-in fetch)
async function changeCampaignLink(slug, newUrl) {
  const res = await fetch(`https://dmlink.app/api/v1/links/${slug}`, {
    method: "PATCH",
    headers: {
      Authorization: `Bearer ${process.env.DMLINK_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ destinationUrl: newUrl }),
  });
  const json = await res.json();
  if (!json.success) {
    throw new Error(`DM Link refused (${res.status}): ${json.error.code} - ${json.error.message}`);
  }
  return json.data; // { changed, previousDestinationUrl, destinationUrl, ... }
}

Python

import os
import requests

def change_campaign_link(slug: str, new_url: str) -> dict:
    res = requests.patch(
        f"https://dmlink.app/api/v1/links/{slug}",
        headers={"Authorization": f"Bearer {os.environ['DMLINK_API_KEY']}"},
        json={"destinationUrl": new_url},
        timeout=15,
    )
    body = res.json()
    if not body.get("success"):
        raise RuntimeError(f"DM Link refused ({res.status_code}): {body['error']}")
    return body["data"]  # { changed, previousDestinationUrl, destinationUrl, ... }

Response

{
  "success": true,
  "data": {
    "campaignId": "cm1a2b3c4d5e6f",
    "changed": true,
    "previousDestinationUrl": "https://chat.whatsapp.com/CURRENT_GROUP",
    "slug": "abc123",
    "position": 1,
    "label": "Primary campaign link",
    "destinationUrl": "https://chat.whatsapp.com/NEW_GROUP",
    "trackedUrl": "https://dmlink.app/r/abc123",
    "updatedAt": "2026-10-02T12:05:00.000Z"
  }
}

Errors and limits

Statuserror.codeMeaning / what to do
400invalid_jsonThe body is not valid JSON. Send {"destinationUrl": "https://..."} with Content-Type: application/json.
400invalid_destinationThe new address is not a complete https:// URL (or is longer than 2048 characters).
401unauthorizedMissing, wrong or deactivated key. Check the Authorization: Bearer dml_... header.
403plan_requiredThe account is not on the Infinity plan. Its keys stop working while it is off Infinity and work again once it is back.
404not_foundNo link with this slug (or Instagram for this ?instagram=) in the key's account. List the Instagram accounts and campaigns to get the right one.
429rate_limitedMore than 60 calls per minute with the same key (the counter resets every clock minute). Wait and try again.

Error messages (error.message) are in English; use error.code, which is fixed, to decide what to do in code. If you list campaigns often, cache the result instead of asking every time.

Response fields

FieldMeaning
changedtrue if the destination changed; false if it already was that one.
previousDestinationUrlWhere the link pointed before this call.
destinationUrlWhere the link points now.
trackedUrlThe tracked link sent in the DMs (never changes).
instagramIn the campaign list: id and username of the campaign's Instagram.
campaignId / positionThe link's campaign and its position in it: 1 = main button, 2 = second button. A link's position never changes.

Example: a full integration flow

  1. Once, at setup: call GET /api/v1/instagram-accounts and pick the Instagram; then GET /api/v1/campaigns?instagram={username}, pick the campaign by name and store the link's slug (usually the one with position: 1).
  2. Your system watches whatever decides the destination (e.g. how many people are in the current WhatsApp group).
  3. When it is time to switch, it calls PATCH /api/v1/links/{slug} with the new address.
  4. If the answer is "success": true, done: DM Link is already sending everyone to the new destination. On an error, retry later (the call is safe to repeat).

Keep it safe