Skip to content
Sendora Cloud
Create account
Module deep-dive

Deep Links — create & resolve

Mint branded short links at runtime with a custom linkData payload, then read that payload back when the recipient opens the link — including on a cold install (deferred deep linking). Works from any client over REST, or via the native SDKs where available.

Create a link (REST)

POST /api/v1/sdk/links is the programmatic mint surface. It works identically from a mobile app or your backend. Authenticate with a publishable key (pk_…) in the x-api-key header — no session, no org role, no projectId in the body (it's inferred from the key).

curl -X POST https://api.sendoracloud.com/api/v1/sdk/links \
  -H "x-api-key: pk_live_YOUR_PUBLISHABLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Challenge from Alice",
    "iosDeepLinkPath": "/challenge",
    "linkData": { "token": "abc123" }
  }'

Only title is required. Attach your own payload as the top-level linkData object (arbitrary JSON, up to 2 KB serialized). The shortcode is auto-generated (squatting defense — no custom codes on this route). Response 201:

{
  "success": true,
  "data": {
    "id": "…",
    "shortcode": "aB3xK9",
    "url": "https://go.sendoracloud.com/aB3xK9",
    "iosDeepLinkPath": "/challenge",
    "linkData": { "token": "abc123" },
    "expiresAt": null,
    "revokeToken": "lrt_…"
  }
}

Share data.url. If you send the optional iosBundleId, it must match a registered app or the mint is refused; omit it and the mint always succeeds.

Revoke a link

Keep the revokeToken from the create response with the link. Sending it makes the revoke final: the link stops resolving and cannot be brought back. Use this for links to private content.

curl -X POST https://api.sendoracloud.com/api/v1/sdk/links/aB3xK9/revoke \
  -H "x-api-key: pk_live_YOUR_PUBLISHABLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "revokeToken": "lrt_…" }'

Without the token the revoke is soft: the link stops resolving at once, but a member can reactivate it from the dashboard (Deep Links → Revoked and deleted links); after that, only the token revokes it. Your publishable key is inside your app, so anyone holding it could send a tokenless revoke; that is why it can be undone. The response says which one happened (data.final). A publishable key can revoke, or read the stats of, only links that an app minted in the same project. Links you create in the dashboard or with a secret key are never affected.

Where app-minted links may point

Anyone can mint a link with your publishable key, so each project sets where those links may send people (Deep Links → Links created by your app):

  • Allow-list (the default for new projects): a link whose own fallbackUrl is not on the project's list opens a “you are leaving to …” page first, and its preview title, text and image are not shown. The list is your registered web apps, your default fallback, your store listings and any hosts you add.
  • Monitor (projects created before this setting existed): any destination. Beyond the daily threshold, links to other sites also open the “you are leaving” page.
  • Open: any destination.

A mint is never refused. A link that omits fallbackUrl inherits the project's own, so it is always on the list. Store listings and the default fallback are matched by their address (not every page on the same host), and a link may never point at another Sendora link.

Which key — publishable or secret?

  • Publishable pk_ — the client-safe key, designed to ship in your app bundle. It has the links:write scope by default, so it can mint from the app directly. /api/v1/sdk/links requires a pk_ and rejects secret keys.
  • Secret sk_ — use only from your own backend, against the dashboard route POST /api/v1/orgs/:orgId/projects/:projectId/links (supports custom shortcodes + 10 KB linkData, and has no per-key rate limit). It returns the raw link row — build the URL yourself as https://go.sendoracloud.com/<shortcode>.

Rate limit: /sdk/links is capped at 60 mints/minute per device (by IP) — each user's device gets its own budget, so a viral “invite a friend” flow from the app scales fine. (The secret-key backend route has no per-key limit if you need very high sustained volume.)

The linkData payload

linkData is stored as JSON and returned byte-for-byte on resolve — nested objects and arrays survive intact, no reserved keys, no type coercion. An omitted linkData comes back as {} (not null). Size cap is the only limit: 2 KB via /sdk/links, 10 KB via the dashboard route.

Resolve a link (read the payload back)

To read linkData back you need the authenticated resolve, which calls GET /api/v1/sdk/links/:shortcode (scope links:read, also default on pk_):

// iOS — authenticated resolve carries linkData back:
SendoraCloud.links?.handleUniversalLink(url: url) { data in
    let token = data?.linkData["token"] as? String
}

// Cold install (deferred) — probabilistic IP-hash match, 2-hour window:
SendoraCloud.checkDeferredDeepLink { data in
    let token = data?.linkData["token"] as? String
}

Note: the purely local handleDeepLink(url:) is an offline URL parse — it returns only the shortcode, not linkData. Use handleUniversalLink / checkDeferredDeepLink to get the payload.

Native SDK create()

The iOS SDK also exposes SendoraCloud.links?.create(_:prewarmKey:completion:) (a convenience wrapper over the REST call above). It's available on iOS 15 from SDK 4.6.0 — 3.9.0–4.5.0 required iOS 16 (the whole package was pinned there for passkeys; 4.6.0 @available-gated passkeys and restored the iOS 15 floor). The REST endpoint above works on any iOS version regardless. There is no server-side idempotency — prewarmKey is a client-side cache hint only, so dedupe on your side if two identical calls must not create two links.

Domain, AASA & deferred install

The default link host is go.sendoracloud.com (a verified custom domain overrides it per project). For warm (installed-app) opens via Universal Links:

  1. Register your iOS app with both its bundle ID and Apple Team ID (missing either → dropped from the AASA file).
  2. Have at least one active link with an iosDeepLinkPath (the global AASA only lists apps that do).
  3. Add applinks:go.sendoracloud.com to your Associated Domains entitlement in Xcode.

Deferred cold-install does not use AASA — it runs through POST /api/v1/sdk/links/match (probabilistic IP-hash on iOS, 2-hour window), and needs only your configured pk_. If you move off the default host, override the SDK's linkHosts allowlist or inbound URLs are rejected.

Project scoping

projectId is inferred from the API key — you never pass it on the SDK/REST path. The key must be project-scoped (an org-wide pk_ with no project is rejected); use the per-project key Sendora auto-mints. configure(projectId:) is optional on SDK 3.0.0+.