Skip to content

API documentation

Create short links and batches of personal codes from any platform: email and SMS senders, push networks, CRMs, n8n / Make / Zapier or your own script.

Overview

Base URL:

https://createlink.link/api/v1

The API works with two things:

  • Link — a short link on your domain that leads to your target page.
  • Personal code — a separate short address for one recipient of a link. Each code carries an optional ref: any label you choose (an email, a subscriber ID, a phone number, a click ID). The API never interprets it — it just gives it back with the code.

Codes are served by the agent on your VPS and pass through the Anti-Bot filter exactly like codes created in the dashboard. They are paid from the same balance: one code = one link from your package.

Requests and responses are JSON (UTF-8). Results can also be downloaded as CSV, which every mailing platform can import.

Authentication

Create an integration key in your dashboard: account menu → API. The key is shown once — store it in your platform's settings. You can have several keys (one per platform) and revoke any of them without affecting the others or your agent.

Authorization: Bearer cl_live_xxxxxxxxxxxxxxxx

The header X-API-Key: cl_live_… works too. Never put the key in a URL or in code that runs in a browser.

curl https://createlink.link/api/v1/account -H "Authorization: Bearer $KEY"

{"user_number": "123456", "links_available": 4870, "billing_enforced": true, "key_name": "Mumara"}

Connecting a platform

Mailing and messaging platforms send at full speed and do not call external services for each recipient. So codes are created before sending, and the platform simply inserts them:

  1. Create a link once: POST /links (or use one from the dashboard).
  2. Before the campaign, send your recipient list: POST /links/{id}/codes with recipients — emails or subscriber IDs.
  3. Get back pairs ref → url (JSON or CSV).
  4. Write each URL into a custom field of the subscriber in your platform — via its API or a CSV import matched by email / ID.
  5. Put that field's merge tag into the letter or message. The platform substitutes each recipient's own link while sending.
Turn off the platform's own click tracking for these links. Otherwise it wraps them into its tracking address and the recipient sees the platform's domain instead of yours.

Step-by-step recipes with ready commands:

Your platform exports a CSV? Send the file as it is — no need to build JSON:

curl -X POST "https://createlink.link/api/v1/links/512/codes?column=1&skip_header=1" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: text/csv" --data-binary @export.csv

And download the result with the column names your platform imports:

curl "https://createlink.link/api/v1/jobs/JOB_ID?format=csv&columns=Email:ref,CL_LINK:url,Name:" \
  -H "Authorization: Bearer $KEY" -o links.csv

Remember: a code lives 60 days and opens the page up to 3 times — create fresh codes for every campaign instead of storing one link in a subscriber field forever.

No list at hand? Send "count": 5000 instead of recipients and distribute the codes in any order.

Tagged links — no lists at all

The simplest way to connect any platform. Turn on tagged for a link (in the dashboard: Link with tag on the link card; via the API: "tagged": true). Then put one address into the message and let your platform append its subscriber tag after r=:

https://go.example.com/aB3dE9?r=%%subscriber_id%%

On the subscriber's first click the platform creates their personal code (paid like any code: 1 link) and every next click of that subscriber goes through the same code with the usual 3-click limit. No export, no import, no fresh batch before every campaign.

  • An untouched tag (%%…%%, […], {…}) or an empty balance does not lose the visitor: they reach the page as from a regular link, just without a personal code.
  • New codes per link are capped per hour, so a bot iterating ?r=1,2,3… cannot drain the balance.
  • Requires agent version 2026.10.18-1 or newer; an older agent ignores r= and works as a regular link.

PATCH/links/{id} with {"tagged": true} switches the mode; the link object then has tagged_url, e.g. https://go.example.com/aB3dE9?r={ref}.

GET/domains — domains ready for links (verified and connected to Cloudflare).

{"domains": [{"id": 9, "domain": "go.example.com"}]}

POST/links — create a link.

FieldTypeDescription
urlstring, requiredTarget page. It is checked against threat databases first, as in the dashboard.
domainstring or idA domain from /domains. "all" (default) spreads links over all your domains.
namestringYour label for the link.
campaignstringCampaign name.
taggedbooleanTurn on tagged mode.
curl -X POST https://createlink.link/api/v1/links -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/offer", "name": "October newsletter"}'

201 Created
{"id": 512, "short_url": "https://go.example.com/aB3dE9", "target_url": "https://example.com/offer",
 "domain": "go.example.com", "all_domains": true, "name": "October newsletter", "campaign": "",
 "archived": false, "created_at": "2026-10-18T09:00:00Z",
 "stats": {"codes": 0, "opens": 0, "clicks": 0, "bots_blocked": 0}}

GET/links — your links, newest first. Query: limit (≤ 200), offset, archived=1 to include archived ones.

GET/links/{id} — one link with its counters.

GET/links/{id}/codes — every code of the link (from the dashboard and the API) with status, opens and clicks. Query: limit (≤ 10 000), offset, format=csv.

Personal codes

POST/links/{id}/codes — create a batch.

FieldTypeDescription
recipientsarray of stringsOne code per item; the item is returned as ref (≤ 255 chars). Up to 100,000 per request.
countintegerInstead of recipients: that many codes without a ref.
modestringauto (default): up to 1,000 codes are returned right away, bigger batches go to a job. sync / async force one way.
webhook_urlhttps URLCalled when the job finishes (see below).
With Content-Type: text/csv (or text/plain) the body is the recipient file itself; query parameters: column — the column with the ref, from 1 (default 1); skip_header=1 — skip the header row; mode, webhook_url as above. Comma, semicolon and tab separators are detected automatically.

Header Idempotency-Key (recommended): any unique string per batch, e.g. your campaign ID. If the connection drops and you repeat the request with the same key, you get the same batch back — nothing is created or charged twice.

curl -X POST https://createlink.link/api/v1/links/512/codes -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" -H "Idempotency-Key: campaign-2026-10-18" \
  -d '{"recipients": ["anna@example.com", "subscriber-1042"]}'

201 Created
{"job_id": "job_4f1c…", "status": "done", "requested": 2, "generated": 2,
 "codes": [
   {"ref": "anna@example.com", "url": "https://go.example.com/aB3dE9_x7Kq…", "code": "aB3dE9_x7Kq…"},
   {"ref": "subscriber-1042",  "url": "https://go2.example.com/aB3dE9_Pz0w…", "code": "aB3dE9_Pz0w…"}
 ], …}

A big batch answers 202 Accepted with "status": "queued" and a status_url. The balance is charged when the batch is accepted; if fewer codes are made, the difference is returned automatically. Not enough links on the balance → 402, nothing is charged.

Jobs & webhooks

GET/jobs/{job_id} — status of a batch: queued → running → done or failed, with generated showing progress. When finished, the response includes codes (pages of up to 10 000: offset, limit). ?format=csv downloads all of them as ref,url,code. Add columns to shape the file for your platform's import: columns=Email:ref,CL_LINK:url,Name: — each item is header:source, where the source is ref, url, code or empty (an empty column). The same works for GET /links/{id}/codes?format=csv.

curl "https://createlink.link/api/v1/jobs/job_4f1c…?format=csv" -H "Authorization: Bearer $KEY" -o codes.csv

Batches survive server restarts: an interrupted job continues where it stopped, without duplicates. Jobs and their results are kept for 30 days.

Webhook: when a job finishes we send POST to your webhook_url (up to 3 attempts):

{"event": "job.finished", "job_id": "job_4f1c…", "status": "done",
 "link_id": 512, "requested": 50000, "generated": 50000}

The webhook carries no codes and no secrets — fetch the result with your key from /jobs/{job_id}. That is why it needs no signature: a forged call cannot give anyone your codes.

Errors & limits

Every error has the same shape:

{"error": {"code": "insufficient_credits", "message": "Not enough links on the balance: …",
           "needed": 50000, "available": 4870}}
HTTPcodeMeaning
400invalid_json, missing_field, invalid_field, unknown_domainFix the request; field names the culprit.
401missing_api_key, invalid_api_keyNo key, a wrong key or a revoked one.
402insufficient_creditsTop up in the dashboard. Nothing was charged.
404not_foundNo such link or job in your account.
409no_domainNo domain is ready for links yet.
413batch_too_largeSplit the list into several requests.
422target_flagged, target_invalidThe target URL failed the threat-database check.
429—Too many requests; wait for Retry-After.
503target_check_unavailableThe URL check is temporarily unavailable; retry in a minute.

Rate limit: 120 requests per minute and 3 000 per hour per key. One batch request can carry up to 100,000 codes, so the limits are about requests, not volume.