Links API
Links endpoints exposed by the Devotel CPaaS API Base path:/api/v1/links
Endpoint count: 14
Worked Links samples
A tracked short link moves through four calls below — mint it (POST /api/v1/links), watch the click stream land (POST to the redirect, GET /api/v1/links//stats), page the full list (GET /api/v1/links), then receive the per-click webhook (short_link.click with an HMAC signature). Substitute your key and tenant ids where shown. Successful requests below use the standard{ data, meta } envelope; the redirect (GET /l/{code}) is the one public surface that bypasses the envelope entirely (it issues a 302 with Location headers, no JSON body).
1. Create a short link
POST /api/v1/linksurl. Drop UTM parameters straight into the destination URL (the API never parses them out — it treats the query string as opaque) and pair with campaign_id when the link belongs to a campaign so clicks roll up to the per-campaign analytics surface. The write rate limit is 20 requests per minute per tenant.
data.code is the 6-character public code (uniform distribution, no ambiguous characters) and data.short_url is the shareable string — {short-domain}/l/{code}. On a tenant with a branded domain (organizations.settings.short_link_domain) the same mint comes back with your vanity host, e.g. https://go.yourbrand.com/l/gH4kM2. Only http:// and https:// URLs mint; any other protocol answers 422.
2. Redirect-click deep dive
GET /api/v1/links/{id}/statshttps://api.orbit.devotel.io/l/gH4kM2 redirects (302) to the destination, records IP + user-agent + referrer, resolves the best-effort country the edge CDN stamps (e.g. Cloudflare’s cf-ipcountry), classifies the click’s traffic quality (bot / preview vs probable-human), then lands in this endpoint’s stats view. Read it back with GET on the link id.
recent_clicks array is the per-click forensics surface (recent-clicks capped at 50 rows; link_clicks rows past that cap drop off this view but still count in clicks_by_day). The is_bot verdict on each click comes from the same traffic-quality classification the per-campaign analytics view reuses, so a human-only CTR is (clicks where is_bot=false) / raw_clicks. Country resolution depends on the deployment’s edge — when the edge supplies no geo header, country reads null rather than a fabricated guess.
3. List links with pagination
GET /api/v1/links?cursor=cursor=<next_cursor> on the next call; the response carries meta.pagination.next_cursor until you reach the tail. Filter by campaign_id when you only want one campaign’s links.
total field saturates rather than scanning the tenant’s whole history. Use the cursor to page safely; never trust total as the exact count past the cap.
4. Signed webhook for click events
A click on a tracked short link fans outshort_link.click to every webhook endpoint registered for the tenant. Verify the signature before reading the body — the header is X-Orbit-Signature (Stripe-style t=<unix_ts>,v1=<hex>; the legacy X-Devotel-Signature is still accepted as a fallback for old queued deliveries). The signed string is ${t}.${raw_body}; verification uses your endpoint secret. A complete per-language handler lives at Verify webhook signatures.
Node.js
short_link.click payload is the consumer-side sibling of email.clicked: link_id + code identify the mint, message_id + campaign_id carry the attribution chain, and the country / user_agent / referrer fields match what the stats endpoint above returns. is_bot + quality_score + quality_reason let you filter preview / bot clicks on receive, the same way the analytics view filters them at read time.
5. Where the short-links guide hands off to API samples
The short-links cookbook walks the whole chain with runnable end-to-end recipes (mint → send → measure → react, then landing pages); the samples above live here so this endpoint page answers “what comes back” without leaving the API reference. Reach for the cookbook when you want a full use case; reach for this page’s sections when you only need the wire shape of one endpoint.Errors worth branching on
These five failures cover the short-link create (POST /api/v1/links), hammered by campaign sends that mint tracked URLs. Each block below is a full{ error, meta } envelope as the API returns it, and the matrix at the bottom answers retry vs surface for the same five classes. For the platform-wide decision table these branches plug into, see the error handling guide.
401 — Unauthorized
401 on this page is the bearer key failing before the route ran — it never means the resource is wrong. Rotate the key or re-mint the scoped token; retrying the same request changes nothing.
403 — Forbidden
403 means the key authenticated but the operation is gated by scope — check the key’s scopes on the developer page; a 403 is never a data-not-found shape.
422 — Schema
422 means the payload did not match the request schema — branch on error.details.field and resend with an https:// destination_url instead of blind-retrying the same body.
429 — Rate
Retry-After header and error.details.retry_after are both set on every 429 — resend the SAME request after the lower of the two.
404 — Not found
60-second retry matrix
List links
GET /api/v1/linksstring (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.List stats
GET /api/v1/links/{id}/statsstring
required
—
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.Get per-campaign link-click analytics
GET /api/v1/links/campaigns/{campaignId}/link-analyticsstring
required
Campaign identifier the links were minted for.
integer
Trailing aggregation window in days (1–365, default 7).
integer
Max top-links rows (1–50, default 10).
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.List clicks
GET /api/v1/links/contacts/{contactId}/clicksstring
required
—
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.List insights
GET /api/v1/links/insightsstring (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.List landing pages
GET /api/v1/links/landing-pagesstring (enum: draft|published|archived)
Filter by lifecycle status.
string
Opaque cursor for the next page (from previous response meta.pagination.cursor).
integer
Number of items per page (default 25; silently capped at 200).
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.Get a landing page
GET /api/v1/links/landing-pages/{id}string
required
Landing page identifier.
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.Get landing page analytics
GET /api/v1/links/landing-pages/{id}/analyticsstring
required
Landing page identifier.
integer
Max rows per breakdown (by campaign / by message), 1–50, default 10.
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.Create links
POST /api/v1/linksstring
Stripe-style idempotency token. Pass a stable, client-generated value (1-255 chars) to dedupe retries on transient timeouts. The same key+credential+path replays the original response for 24h on 2xx (5min on 4xx, 30s on 5xx). Returns 409 if a concurrent request with the same key is already in flight; replayed responses include the
Idempotency-Replay: true response header.string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.Create a landing page
POST /api/v1/links/landing-pagescontent is the builder block list, re-validated in depth by the service. The page starts in draft and only mints a trackable short URL once published.
string
Stripe-style idempotency token. Pass a stable, client-generated value (1-255 chars) to dedupe retries on transient timeouts. The same key+credential+path replays the original response for 24h on 2xx (5min on 4xx, 30s on 5xx). Returns 409 if a concurrent request with the same key is already in flight; replayed responses include the
Idempotency-Replay: true response header.string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.string
required
Human-readable page title.
string
Optional URL slug. Auto-derived from the title when omitted; unique per tenant.
any
Builder block list (page structure). Validated in depth by the service.
string
Optional campaign to attribute visits/conversions to.
Publish a landing page
POST /api/v1/links/landing-pages/{id}/publishpublished and returns the page with short_url populated.
string
required
Landing page identifier.
string
Stripe-style idempotency token. Pass a stable, client-generated value (1-255 chars) to dedupe retries on transient timeouts. The same key+credential+path replays the original response for 24h on 2xx (5min on 4xx, 30s on 5xx). Returns 409 if a concurrent request with the same key is already in flight; replayed responses include the
Idempotency-Replay: true response header.string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.Update a landing page
PATCH /api/v1/links/landing-pages/{id}content is re-validated in depth when present.
string
required
Landing page identifier.
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.string
Human-readable page title.
any
Builder block list (page structure). Validated in depth by the service.
string | null
Campaign to attribute visits/conversions to. Pass null to clear.
string (enum: draft|archived)
Move the page between
draft and archived.Delete links
DELETE /api/v1/links/{id}string
required
—
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.Delete a landing page
DELETE /api/v1/links/landing-pages/{id}string
required
Landing page identifier.
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.204 No Content