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
}
}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.
Which key — publishable or secret?
- Publishable
pk_— the client-safe key, designed to ship in your app bundle. It has thelinks:writescope by default, so it can mint from the app directly./api/v1/sdk/linksrequires apk_and rejects secret keys. - Secret
sk_— use only from your own backend, against the dashboard routePOST /api/v1/orgs/:orgId/projects/:projectId/links(supports custom shortcodes + 10 KBlinkData, and has no per-key rate limit). It returns the raw link row — build the URL yourself ashttps://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:
- Register your iOS app with both its bundle ID and Apple Team ID (missing either → dropped from the AASA file).
- Have at least one active link with an
iosDeepLinkPath(the global AASA only lists apps that do). - Add
applinks:go.sendoracloud.comto 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+.